Scalaという言語をご存知ですか?
Javaと同じくコンパイルされるとclassファイルになり、実行時はJVM上で動作する、オブジェクト指向+関数型のプログラミング言語です。
Scalaを開発したのはJavaのgenericsの設計を手がけたり、javacの開発をしていた経歴も持つMartin Odersky氏。
Scalaは後発の言語ということもあって、Javaを書いている時に感じる冗長さに対する様々な解が用意されています。
本記事では、ScalaとJavaのコードを比較しながら、JavaユーザがScalaに移った際に得られるメリットを提示していきます。
尚、序盤のサンプルコードはJavaユーザに伝わりやすいように、returnを明記したり、メソッドは必ず{ }で囲むなど、極力Javaっぽい記述をしています。
2011/12/12 : その2書いた
ScalaはJavaの機能をほぼそのまま使うことができます。
以下はJavaのFileReaderなどのクラスを使ってファイルを1行ずつ読む処理を、Scalaで記述したものです。
//【Scala】
import java.io.File
import java.io.FileReader
import java.io.BufferedReader
class Foo {
def read() {
val reader = new BufferedReader(new FileReader(new File("temp.txt")))
try {
var line : String = null
while ({ line = reader.readLine; line != null }) {
println(line)
}
} finally {
reader.close
}
}
}
見ての通り、細部の違いこそあれ大枠はJava使いにとっては馴染み深い記述になっています。
Scalaは代入式が値を返さないため、(line = reader.readLine) != nullと書けないなど、いくつかの部分で振る舞いの違いはありますが、その辺りを把握してしまいさえすれば、型推論があって、continueがなくて、プリミティブ型がなくて、引数に再代入できないJavaくらいの軽い気持ちで使うことができるようになります。
Javaのライブラリをそのまま利用することもできるので、新しい言語にありがちな「このライブラリがなくて困る」という事態に遭遇することはほとんど経験しなくて済みます。
最適化の状態にもよりますが、実行速度もJavaに近い速度で動きます。
Javaを使っていると、大した処理でもないのにやたらと長い記述が必要になることがあります。
たとえば初期化。Javaには長い名前のクラス名がたくさんあります。
以下はDOMでXMLを解析する際の前処理です。
//【Java】
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("foo.xml"));
ただ初期化するだけだというのに、ひどく長い文字数が費やされています。Genericsの記述などが絡めば、初期化だけで平気で折り返しが必要になることもあります。
Scalaではまず、クラス名を明記しなくてもコンパイラが型を推測してくれる型推論が用意されています。
なので、記述的にはこんな風になります。
//【Scala】
val factory = DocumentBuilderFactory.newInstance();
val builder = factory.newDocumentBuilder();
val doc = builder.parse(new File("foo.xml"));
型推論のお陰で、記述がちょっとスッキリしました。
さらにScalaでは、importしたクラスに別名を付ける別名importという機能も用意されています。
別名importは下記のような記述になります。
import javax.xml.parsers.{ DocumentBuilderFactory ⇒ DBFactory }
これを利用した上で、最初のJavaのコードを記述すると、こんな風になります。
//【Scala】
val factory = DBFactory.newInstance
val builder = factory.newDocumentBuilder
val doc = builder.parse(new File("foo.xml"))
最初のJavaのコードが185文字だったのに対して、119文字に減っています。しかも削減したのは自明の部分、冗長な部分だけです。
ついでに型推論によって余分なimportも減ります。Javaの例では3つのクラスをimportする必要がありますが、Scalaの例ではDocumentBuilderFactoryのみimportすれば、残りの2つはコンパイラが勝手に推論してくれます。
JavaではListや配列の初期化がけっこう面倒です。普通に配列を初期化するだけなら、こんな風に書けます。
//【Java】
String[] array = {"abc", "def", "ghi"};
これは楽なのですが、上記の式が使えるのは初期化の時のみ。下記のような記述はエラーになります。
//【Java】
String[] array = null;
array = {"abc", "def", "ghi"};
なので初期化以外の場所、たとえば引数の中でさくっと配列を渡したい場合は、こんな記述になります。
//【Java】
foo(new String[] { "abc", "def", "ghi" });
Listの初期化の場合は、Arrays.asListを使うのが速いでしょうか。
//【Java】
Arrays.asList("abc", "def", "ghi")
Scalaは初期化がもう少し簡単です。Array("要素1", "要素2", ...)のように書けば配列が、List("要素1", "要素2", ...)のように書けばListが生成できます。
//【Scala】
val array = Array("abc", "def", "ghi")
val list = List("abc", "def", "ghi")
引数の中でも、再代入の際でも、どこでもこれ1つでListやArrayを生成できるので、Javaよりちょっと楽です。
Javaでは引数にデフォルト値を入れたい場合、メソッドを複数個定義する必要が出てきます。
例えば、消費税を計算する際に、デフォルトを5%にして、任意で税率を引数に渡せる処理を書く場合、以下のように2つメソッドを定義します。
//【Java】
public int shohizei(int kane) {
return shohizei(kane, 1.05f);
}
public int shohizei(int kane, float zei) {
return (int) (kane * zei);
}
Scalaではスクリプト言語などで良く用いられているデフォルト引数が用意されています。
デフォルト引数を使えば、以下のように1つのメソッドを定義するだけで上のコードと同じ振る舞いをさせることができます。
//【Scala】
def shohizei(kane : Int, zei : Float = 1.05f) : Int = {
return (kane * zei).toInt
}
しかも静的型付け言語なので、RubyやPythonなどの動的型付け言語では利用できないオーバーロードもJavaと同じく利用可能です。
但し、デフォルト引数とオーバーロードが絡むと、組み合わせによっては呼び出されるメソッドを勘違いしやすい記述になってしまうこともあるので、ご利用は計画的に。
ついでに、Scalaでは呼び出す際にどの引数に対して値を入れるか指定できる名前付き引数も用意されています。
名前付き引数を使えば、以下のように3つの引数のうち、2つ目と3つ目に値を指定するという記述もできるようになります。
//【Scala】
/** 3つのの引数を取るメソッド */
def foo(c1 : Char = 'A', c2 : Char = 'B', c3 : Char = 'C') {
println(c1.toString + c2.toString + c3.toString)
}
// そのまま呼び出すとデフォルト引数が適用される
foo()
// => ABC
// 1つ引数を入れると、1つ目の引数に値が適用される
foo('D')
// => DBC
// 名前付きで指定すれば、2つ目と3つ目に適用するような指定もできる
foo(c2 = 'E', c3 = 'F')
// => AEF
便利。
Javaでは発生しうる例外をメソッドの宣言時に記述するthrowsというキーワードがいます。
//【Java】
public Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:sqlite:hoge.sqlite3");
}
throwsを指定されたメソッドは、利用する際に指定された例外をcatchかthrowsする記述を入れないとコンパイルエラーになるので、例外処理を忘れずに記述できるという点で効果があります。
ただ、多くの開発現場でそれらは適切に使われずに、RuntimeExceptionを継承したオリジナルの例外でラップしたり、面倒だからExceptionでひとまとめにcatchするなど、適切に使われていないケースも良く見かけます。
//【Java】
// 面倒だからExceptionでまとめてthrows
public void hoge() throws Exception {
// 処理
}
// RuntimeExceptionでラップしてthrow
public void fuga() {
try {
// 処理
} catch (Exception e) {
throw new RuntimeException(e);
}
}
便利な機能ではあるけど、人間が管理しようとするとthrowsはけっこうヘビーです。
というわけで、Scalaではthrowsを亡き者にしました。
//【Scala】
// throwsを指定しなくても呼び出せる
def getConnection: Connection = {
DriverManager.getConnection("jdbc:sqlite:hoge.sqlite3");
}
Javaのクラスと連携するために、アノテーションでthrowsを指定することはできますが、Scalaだけを使ってる場合は関わることはなくなります。
Javaの文字列の比較は、equalsメソッドを利用します。
== を使用した場合は同一の参照先を指しているかを比較することになるので、その文字列を生成した際の状況によって結果が変わったりします。
//【Java】
String str1 = "テスト";
String str2 = "テスト";
String str3 = new String("テスト");
System.out.println(str1 + " == " + str2 + " = " + (str1 == str2));
//=> テスト == テスト = true
System.out.println(str1 + " == " + str3 + " = " + (str1 == str3));
//=> テスト == テスト = false
慣れてしまえばそれまでなんですが、ここは多くの人が一度は引っかかるポイントです。
ScalaはIntやDoubleといった数値についてもプリミティブ型ではなくオブジェクトなので(コンパイラが裏でプリミティブ型にして速度を落とさないようにはしているらしい)、参照先を比較する == の出番というのはほとんどなくなってしまいます。
というわけで、Scalaでは == は値を比較するようになりました。
//【Scala】
val str1 = "テスト"
val str2 = "テスト"
val str3 = new String("テスト")
str1 + " == " + str2 + " = " + (str1 == str2)
//=> テスト == テスト = true
str1 + " == " + str3 + " = " + (str1 == str3)
//=> テスト == テスト = true
new Stringした値も、== すればtrueと判定されます。
参照先の比較を行う場合は、eqを用います。
//【Scala】
str1 + " eq " + str2 + " = " + (str1 eq str2)
//=> テスト == テスト = true
str1 + " eq " + str3 + " = " + (str1 eq str3)
//=> テスト == テスト = false
これで入門時に罠にかかる人も減るのではないでしょうか。
Scalaは省略できることは省略することを良しとしている言語です。
例えばJavaで2つの数を足して返すメソッドを記述すると、以下のようになります。
//【Java】
public int sum(int i1, int i2) {
return i1 + i2;
}
Scalaで上記のコードを極力似たように記述すると、こうなります。
//【Scala】
def sum(i1 : Int, i2 : Int) : Int = {
return i1 + i2;
}
Scalaではprotectedやprivateは予約語として存在していますが、publicはありません。「何も指定しない=public」です。
この状態だとpublicがない以外はJavaと大して変わりません。省略できるところを1つずつ削っていってみましょう。
まず、行末がセンテンスの切れ目になることは自明のことです。わざわざ自明のことのためにセミコロンを何度もタイプするのは無駄です。Scalaでは1行に2つの文を詰め込みたい時を除いてセミコロンは必要ありません。
//【Scala : セミコロン抜き】
def sum(i1 : Int, i2 : Int) : Int = {
return i1 + i2
}
また、Scalaでは最後に評価した値が自動的に戻り値になります。なのでreturnはいりません。
//【Scala : return抜き】
def sum(i1 : Int, i2 : Int) : Int = {
i1 + i2
}
文が1行の場合は、わざわざメソッドの処理を{ }で括ってブロックを作る必要はありません。
//【Scala : { }抜き】
def sum(i1 : Int, i2 : Int) : Int = i1 + i2
i1 + i2の結果がIntなのは分かりきったことです。なので戻り値の型は指定しなくてもコンパイラが推測してくれます。
//【Scala : 戻り値の型指定抜き】
def sum(i1 : Int, i2 : Int) = i1 + i2
ふぅ、スッキリしました。
ちなみに引数がない場合は、括弧ごと省略できます。
//【Scala : 引数の括弧省略】
def hello = println("hello")
このようにScalaはいろんな記述を省略できます。
慣れないうちは省略された内容から原型が想像できないという副作用もありますが、慣れてしまえばタイプ数が減らせるありがたい機能だと思えるようになります。
Javaでコードを書いていると、いろんな箇所にtry〜catchの記述が現れます。
//【Java】
try {
// 処理
} catch (SQLException e) {
e.printStackTrace();
}
このよくある処理を部品化しようとすると、インターフェース作ってそれを実装したクラスを共通のtryが入ったクラスの中で呼び出すような、ちょっとした構造を考える必要があります。
Scalaには引数に関数を渡すことができる高階関数があります。これを使えばtry〜catchとか、最後に必ずcloseするとか、そういった定形の処理を簡単に記述できます。
たとえばこんな感じです。
//【Scala】
/** メソッドを引数に取って、try〜catchの中で実行させるメソッド */
def tryCatch[T](func : ⇒ T) : Option[T] = {
try {
Some(func)
} catch {
case e : Exception ⇒ None
}
}
/** 上のtryCatchメソッドを使って割り算するメソッド */
def divide(i : Int, j : Int) : Option[Int] = tryCatch {
i / j
}
// 実行してみる
divide(10, 3)
// => Some(3)
divide(10, 0)
// => None
まずtryCatchという、例外が起きたらNoneを、それ以外だったら処理した結果を返すメソッドを用意します。Optionという型が出てきていますが、これは結果をラップするなどの用途で使われるScalaの型です。
次にdivideというメソッドで、tryCatchメソッドを呼び出しつつ、中で i / j を実行するように記述しています。
divideを呼び出すと、iをjで割った値が返ります。この時、0で除算すると当然例外が起きるわけですが、その場合はtryCatchの中でcatchされてNoneが返ります。このtry〜catchはどんな処理に対してでも適用できます。
高階関数を使うと、Javaでは部品化するのに記述量がけっこうかかりそうなことが、こんな風に割とあっさり記述できます。
Scalaはfor文が張り切って拡張されています。
たとえば九九の計算結果を出力するプログラムをJavaで書くとこんな感じになります。
//【Java】
for (int i = 1; i < 10; i++)
for (int j = 1; j < 10; j++)
System.out.println(i * j);
Scalaでは下記のようにネストした記述を1つにまとめることができます。
//【Scala】
for (i ← 1 to 9; j ← 1 to 9)
println(i * j)
上記の記述で、ちゃんとJavaで書いたのと同じ1×1〜9×9までの計算をしてくれます。
入れ子は2つだけじゃなく、3つでも4つでもできます。
//【Scala】
for (i ← 1 to 3; j ← 1 to 3; k ← 1 to 3; l ← 1 to 3)
println(i * j * k * l)
for文の中には条件も書けてしまったりもします。たとえば、iとjのどちらか1つ以上が偶数だった場合のみ処理をするという記述はこんな感じ。
//【Scala】
for (i ← 1 to 9; j ← 1 to 9 if i % 2 == 0 || j % 2 == 0)
println(i * j)
ついでにyieldを使えば、for文の中で計算した内容でListを作ることもできます。
//【Scala】
// 1 * 1 〜 3 * 3 までの計算結果を格納
val list = for (i ← 1 to 3; j ← 1 to 3)
yield i * j
println(list)
// => Vector(1, 2, 3, 2, 4, 6, 3, 6, 9)
これらはfor文の機能のごく一部です。for文、多機能過ぎです。
プログラムを書いていると、Listの中身を回してちょっと加工して、新しいListを作る、という要件に頻繁に出くわします。
Scalaには、ループを記述してその中に処理を書きこまなくても簡単にデータの加工ができる、便利な機能が大量に用意されています。たとえば以下のような。
//【Scala】
// こんなListがあったとさ
val list = List(1, 2, 3, 5, 3, 5, 7, 8)
// ユニークな要素を抽出する
list.distinct
// => List(1, 2, 3, 5, 7, 8)
// 偶数と奇数で分けてみる
list.groupBy(x => x % 2)
// => Map((1,List(1, 3, 5, 3, 5, 7)), (0,List(2, 8)))
// 合算してみる
list.sum
// => 34
// 左から順に乗算していく
list.reduceLeft((x, y) => x * y)
// => 25200
// すべての値を2倍したListを作る
list.map(x => x * 2)
// => List(2, 4, 6, 10, 6, 10, 14, 16)
// 2つの要素の差分を取る
List(1, 2, 3, 4, 5) diff List(1, 4, 5)
// => List(2, 3)
しかもこれらの機能は、ScalaのList機能だけじゃなく、JavaのList機能でも利用することができます。
//【Scala】
// ScalaのListの機能をJavaのクラスに対しても利用できるようにする
import scala.collection.JavaConversions._
// Java使いにはお馴染みのArrayListを用意
import java.util.ArrayList
val list = new ArrayList[Int]
// 要素を追加
//(Scalaは引数が1個の場合はいろんな省略記法が可能)
list.add(1)
list add(2)
list add 3
// 合計を出してみる
list.sum
// => 6
// 文字列にしたListを作ってみる
list.map(x => "x=" + x)
// => ArrayBuffer(x=1, x=2, x=3)
と、こんな風にScalaでは既存のJavaのクラスに新たな機能を追加したかのような動きをさせることもできます。
これは暗黙の型変換というちょっとしたトリックを使って実現しています。
というわけで、Java使いの皆さんに馴染み深そうな部分でScalaのメリットを並べてみました。
Scalaの説明というと、関数型がイミュータブルで並列処理だとか、Traitでmix-inして多重継承だとか、遅延でクロージャで高階なカリーだとか、Javaにはない言葉がいっぱいでてきます。
それらを覚えればもっと処理を効率よく書けるようにはなりますが、そうした機能を学ばなくても、この記事で示したようなメリットはすぐに得ることができます。
デメリットとして、現状ではまだIDEが完成しきっていなかったり、コンパイラがJavaよりもっさりしていたり、Scalaを仕事で使う機会なんてほとんど出会えなかったり等、新興の言語にありがちな問題をいくらか抱えてます。
それらの問題が十分に解決されれば、今後、この言語が主流になっていくこともありえるんじゃないかと個人的には予想しているのですが、どうでしょう。
公式サイト
http://www.scala-lang.org/
Scala本体のダウンロードはここから。いろいろドキュメントも置いてあります。
First Steps to Scala
http://www.artima.com/scalazine/articles/steps.html
導入から基礎的な使い方までの説明がコンパクトにまとまってます。英語ですが図とコードだけ読んでも十分役立ちます。
これから15分で Scala を始めるための資料
http://diary.overlasting.net/2011-02-11-1.html
とりあえずHello Worldしてみようと思った人向けの記事です。紹介されている書籍やリンクも役立ちます。
RubyからScalaに乗り換えた15くらいの理由
http://wota.jp/ac/?date=20100426
Ruby使い向けにScalaの特徴をまとめた記事。これを読むとScalaを使いたくなってくるはず。
Scala開眼
http://www.h7.dion.ne.jp/~samwyn/Scala/scalaindex.htm
インストールから基礎的な書き方まで、幅広く解説しています。
Scalaメモ(Hishidama's Scala Memo)
http://www.ne.jp/asahi/hishidama/home/tech/scala/index.html
インストールから基礎的な書き方まで、幅広く解説しています。IDEの導入方法も載ってます。
Getting Started | Scala IDE for Eclipse Space
http://www.assembla.com/wiki/show/scala-ide/Getting_Started
EclipseのScalaプラグインの導入方法です。利用する際はxmxを多めに指定することをオススメします。我が家では2gにしてます。
刺激を求める技術者に捧げるScala講座
http://itpro.nikkeibp.co.jp/article/COLUMN/20080613/308019/
ITProでかれこれ2年以上連載されている記事。最近はScalaのWebフレームワークのLiftも取り扱ってます。
scalaとか・・・
http://d.hatena.ne.jp/xuwei/
Scalaの情報を取り扱っているブログ。とりあえずリーダーに登録しておきましょう。
Actorはじめました
http://wota.jp/ac/?date=20101014
Scalaのウリである並列処理を実現するActorの使い方が載ってます。
Twitterが分散フレームワーク「Gizzard」公開! Scalaで書かれたShardingを実現するミドルウェア
http://www.publickey1.jp/blog/10/twittergizzard_scalasharding.html
ScalaはTwitterでも使われているようです。
foursquare が Scala/Lift に移行したぜ!ヤァ!ヤァ!ヤァ!
http://subtech.g.hatena.ne.jp/secondlife/20100216/1266301844
Scalaはfoursquareでも使われているようです。
Odersky先生のTwitterアカウント
http://twitter.com/#!/odersky
Scala関連の情報がいろいろ流れてます。
Scalaプログラミング入門
http://www.amazon.co.jp/dp/4822284239
Scalaの入門書です。Java使いが入門する際の参考書はこれが良いのではないかと。