Scalaという言語をご存知ですか?
Javaと同じくコンパイルするとclassファイルになり、出来上がったclassファイルをJadなどで逆コンパイルするとJavaのソースを生成できてしまうという、かなりJavaに近いプログラミング言語です。
JDKの機能やJavaのライブラリをそのまま利用することができるので、Javaプログラマが既存の知識を活用できる言語でもあります。
今回はJavaを利用している際に発症しがちな様々な症例を題材として、Scalaを利用した際に得られるメリットを紹介していきたいと思います。
興味のある方はその1も併せてご覧ください。
最近のパソコンはCPUのコア数も増えてきて、4コアとか6コアのマシンも割と手頃な価格で手に入るようになってきました。
でも、そういったマシンのCPU使用率を見てみると、たいていは1つか2つのコアが働いているだけで、残りのコアは暇そうにしていたりします。
そんな姿を見ていると「暇そうにしてるヤツも働けよ」という気持ちに襲われることも多いのではないでしょうか。
Scalaにはそんな怠け者たちを鞭打って働かせることができる様々な機能が用意されています。
たとえば指定フォルダの配下にある複数のファイルを解析して、各ファイルごとの分析結果を出すという処理があったとします。Scalaはこうした処理をものすごく手軽に並列化できます。
//【Scala】
// JavaのListをScalaっぽく扱うためにJavaConversionsを呼び出しておく
import scala.collection.JavaConversions._
// 指定ディレクトリ配下のファイル一覧取得
val list = new File("dir").listFiles()
// 取得したファイル一覧に対して「par」と言ってからforで回す
for (file <- list.par) {
// ここに各ファイルに対する処理を書く
}
上記のようにCollectionに対してparと発言すると、ループ内の処理が並列で実行されます。並列化時のスレッド数は4コアなら4スレッド、Core i7の擬似8コアなら8スレッドというように、CPUのコアの数によって自動で調節されます。
この機能は並列コレクション(Parallel Collection)と呼ばれています。
並列コレクションを利用すると、下図のようにすべてのコアが酷使されるようなコードを簡単に書くことができます。もちろん実行速度も格段に向上します。
Scalaにはこれ以外にもいくつかの並列処理のための仕組みが用意されています。代表例としてErlangで有名なActorがありますが、この子は説明が面倒なのでパスして他の簡単な子を紹介しましょう。
例えば2つの重い処理を並列で実行して、両方が終わったら次の処理をする、というフローを書きたくなった場合はscala.concurrent.opsのpar(さっき使ったのはCollectionのpar)を使ってみましょう。
//【Scala】
// parをimportしておく
import scala.concurrent.ops.par
// 0〜10000までの数値を合算する関数
def func1 = (0 to 10000).sum
// 100000〜20000までの数値を合算する関数
def func2 = (10000 to 20000).sum
// 上の2つの関数を並列実行
par( func1, func2 )
//=> (50005000,150015000)
上記のように、par(関数1, 関数2)のように実行すると2つの関数が並列で実行され、双方の処理が終わったタイミングで処理結果が返ってきます。
自前で実装しようとすると何かと面倒な並列処理ですが、Scalaのこの辺りの機能を使えばそれほど苦労せずにCPUを酷使するコードが書けるようになります。
普段は余裕そうな顔をしている最新のCPUが、ファンをフル回転させて辛そうにしている姿を見るのは、なかなかに良いストレス解消になります。
Javaでは変数にfinalを付けることで、値の再代入を禁止することができます。
//【Java】
final String FD = "Final Destination";
// finalな変数に再代入しようとする
FD = "Final Dead Coaster";
//=> コンパイルエラー : The final local variable FD cannot be assigned.
finalを付けておくと誤って再代入してしまう等のミスが防げるので、少しだけコードの安心感が増します。
安全性を求め、再代入が必要ない変数には全てfinalを付けたいと考えるfinal病を罹患しているJavaプログラマも、少なからず存在します。
しかしすべてに対してfinalを付加していくと、constがconstして君が泣くまでconstをやめないCのソースのように、finalがfinalしてファイナルデッドコースターなJavaのソースができあがってしまい、見栄え的にはあまり美しくなりません。
そこでScalaの出番です。
Scalaは変数宣言時に「var」か「val」、どちらかのキーワードを利用します。「var」は一般的な再代入可能な変数です。
//【Scala】
// 再代入できる
var i = 0
i = 1
「val」は再代入が禁止された変数です。valで宣言した変数に再代入を行うとコンパイラがエラーが出ます。
//【Scala】
// 再代入できない
val i = 0
i = 1
//=> コンパイルエラー : reassignment to val
このようにScalaでは、再代入の可能/不可能が「var」と「val」という同じ文字数のキーワードで切り替えることができます。
Scalaを使えばfinal病に蝕まれたあなたも、すっきり安全なコードが書けるようになるでしょう。
変数だけでなく、値が書き換えられることがない不変のListがデフォルトで利用されているなど、Scalaはfinal病の患者にとって安らげる環境が揃っています。
Javaのコードを読んでいると頻繁且つ大量に目にするsetter/getterの羅列。
たとえば緯度と経度を保持するだけのクラスでも、Javaではこんな素敵なコードが出来上がってしまうことがあります。
//【Java】
class Geo {
private double lat;
private double lon;
public Geo(double lat, double lon) {
this.lat = lat;
this.lon = lon;
}
public double getLat() { return lat; }
public void setLat(double lat) { this.lat = lat; }
public double getLon() { return lon; }
public void setLon(double lon) { this.lon = lon; }
public String toString() {
return "Geo [lat=" + lat + ", lon=" + lon + "]";
}
}
Java7で対策が検討されていたような気もしますが結局盛り込まれなかったようなので、今後も当分の間は彼らとのお付き合いが必要になりそうです。
上記コードの大部分は神様仏様Eclipse様に自動生成してもらっていますが、たとえコードが自動生成できても後で読む際にげんなりするという症状は残ります。
Scalaはこうした定形のコードをわざわざ記述しなくても、フィールドを書いておけば後はコンパイラがいい感じにやってくれます。
例えば以下のようなクラスがあったとします。
//【Scala】
class Geo ( var lat: Double, var lon: Double )
Scalaではコンストラクタが受け取る引数は、上記のようにクラス名の後の括弧内に書きます。
上記のコードをコンパイルした後、出来上がったclassファイルを逆コンパイルすると、下記のようなコードが出力されます(一部省略したり改行を削ったりしてます)。
//【Java】
public class Geo implements ScalaObject {
private double lat;
private double lon;
public double lat() { return this.lat; }
public void lat_$eq(double paramDouble) { this.lat = paramDouble; }
public double lon() { return this.lon; }
public void lon_$eq(double paramDouble) { this.lon = paramDouble; }
public Foo(double lat, double lon){ }
}
見ての通り、latとlonというprivateフィールドと、それに対するsetter/getterっぽいメソッドが自動で実装されています。
この子は以下のように扱えます。
//【Scala】
// 初期化
val geo = new Geo( 130.0, 35.5 )
// 値の設定
geo.lon = 36.5
// 値の取得
println(geo.lat)
//=> 130.0
値の変更はgeo.lon = 値のように変数を直接操作しているかのような記述になっていますが、逆コンパイル結果を見ての通り、変数のlon自体はprivateメソッドになっており直接操作はできません。実際にはlon_$eqが呼び出されています。
こうした機能のお陰で、Scalaではコード上から単純なsetter/getterを消し去ることができます。
この時、変数をprivateで指定すればsetter/gertterは生成されません。また、varではなくvalで宣言した場合はgetterのみ生成されます。
//【Scala】
// valならgetterのみ生成される
class Geo ( val lat: Double, val lon: Double )
// privateを付ければgetterも生成されない
class Geo ( private val lat: Double, private val lon: Double )
// 普通のフィールドでも同じようなものが生成される
class Geo {
val lat = 0.0
val lon = 0.0
}
これに加えてScalaにはさらに強力なcase classという子がいます。この子はtoStringやcopy、equalsなどの機能も自動で追加してくれます。
使い方は宣言時にcase class クラス名と書くだけです。
//【Scala】
// この記述だけでgetter、setter、toString、equals、copy等が完備されたクラスになる
case class Geo(val lat: Double, val lon: Double)
// 初期化
val geo = Geo(35.5, 130.0)
// toStringが自動で実装されるのでprintlnするとそれっぽく出力される
println(geo)
//=> Geo(35.5, 130.0)
// コピー機能
val geo1 = geo.copy()
// 一部の引数だけ指定の値にしてコピーとかもできる
val geo2 = geo.copy( lon = 140.0 )
println( geo2 )
//=> Geo(35.5,140.0)
// ==で比較できる
println( geo1 == geo2 )
//=> false
こんな感じでcase classを利用すればsetter/getterのお供で書いていた様々な定形処理ともお別れすることができます。
RubyやPythonでは普通に実行できる、複数の戻り値を返す処理。
#【Ruby】
# 値を2つ返す関数
def foo(i)
return i * 10, i * 20
end
# 2つの値を受け取れる
x, y = foo(10)
たまに欲しくなる機能ですが、質実剛健、不器用ですからを売りとするJavaさんにはこういった手軽な記述はできません。なので、そのためだけにクラスを作るとか、配列で値を返すといった処理をする必要があります。
//【Java】
// わざわざクラスを用意してみる
public class Foo {
int x;
int y;
public Foo(int x, int y) {
this.x = x;
this.y = y;
}
// 以下略
}
// 戻り値にクラスを使う
public Foo foo(int i) {
return new Foo(i * 10, i * 20);
}
クラスを作ると上記のように無駄に長いコードが必要になったりします。
でも、それが面倒だからといって値を配列に入れて返したりすると、戻り値の数が保証されなかったり複数の型を返せなかったりするなどの問題もあって微妙です。
Scalaでは戻り値の数や型の安全性なども考慮した上で、複数の戻り値を返す方法が用意されています。
下記はScalaでStringとIntの2つの戻り値を返す場合の例です。
//【Scala】
def test(): (String, Int) = {
return ("国語", 80)
}
val (kamoku, tensu) = test()
println(kamoku, tensu)
//=> (国語, 80)
testというメソッドで戻り値に(String, Int)という型を指定してます。これはScalaのTupleという機能です。
上記では2つの値を返していますが、Tupleは1〜22個までの値を扱えるのでもっとたくさんの値を返すことができます。
//【Scala】
def test(): (String, Int, Double, Int) = {
return ("国語", 80, 61.2, 7)
}
val (kamoku, tensu, hensati, junni) = test()
println(kamoku, tensu, hensati, junni)
//=> (国語, 80, 61.2, 7)
さすがに上記のような情報を扱う場合はTupleよりClassを作った方が適切だと思いますが、2つのちょっとした値を扱おうとした場合などにはとても便利です。
Tupleは型や個数が違うものを入れようとするとコンパイルエラーになります。なので個数や型が決まった情報を扱う場合は、配列やListを使うよりも安心感のあるコードを書くことができます。
JavaはHello Worldするだけでもこれだけのコードを書かないといけなかったりします。
//【Java】
class Sample {
public static void main(String[] args) {
System.out.println("hello world");
}
}
しかもコンパイラ言語なので、javacか何かでコンパイルした後に実行コマンドを打たなければいけません。
日常的にはIDEを使えば大した問題にはなりませんが、リモートで操作しているサーバ上でちょっとだけコードを動かしたいなんてことを思った時などにはけっこう不便を感じることもあります。
Scalaもコンパイラ言語なのでJavaと同じようにその辺りは不器用かと思いきや、ちゃんとそれっぽい機能が用意されています。
まず用意されているのが、スクリプトモードでの実行。例えば以下のような1行のコードが書かれた、hello.scalaという名前のファイルがあったとします。
//【Scala】
println("hello world")
下記のようにコマンドを実行すると、上記のファイルをコンパイルなしで実行することができます。
$ scala hello.scala
hello world
スクリプト言語っぽい処理の実行の仕方ですね。
JVMの起動コストとかコンパイルにかかる時間とかもあって、挙動はちょっともっさりした感じになりますが、簡易なコードをさらっと実行したい場合などには便利です。
また、ファイル名を指定せずにscalaコマンドを実行すると、PythonやRubyなどでお馴染みの1行ずつコードを書いて実行できるREPLでScalaのコードを実行することができます。
$ scala
scala> val i = 3
i: Int = 3
scala> i + 2
res0: Int = 5
スクリプトモードやREPLではclassやmain関数を書かなくてもコードが実行できます。
普通にコンパイルして実行する場合はmain関数が必要になります。
【Scala】
object Sample {
def main(args: Array[String]) {
println("hello world")
}
}
但し、Appを継承すると、コンパイルして実行する場合でもmain関数を書かなくて良くなります。
【Scala】
object Sample extends App {
println("hello world")
}
こんな感じで、実行方法1つ取ってもScalaは何かと多彩な方法を用意しています。
Scalaの文字列は、基本的にjava.lang.Stringと同じものです。なのでcharaAtとかindexOfなどのお馴染みのメソッドが使えます。
//【Scala】
"abc".charAt(1)
//=> b
"abc".indexOf('b')
//=> 1
これに加えて、Scalaではいくつかの関数が追加で使えるようになっています。たとえば(0)がcharAt(0)と同じ意味になったり、forで1文字ずつ処理ができたり。
//【Scala】
// 括弧でcharAtと同じ振舞をする
"abc"(1)
//=> b
// forやforeachで1文字ずつ処理できる
for(c <- "abc")
println(c)
//=> a
//=> b
//=> c
// アスタリスクで文字の繰り返しを取得できる
"abc" * 3
//=> abcabcabc
// 文字列の一部を切り出す(要素数を超過してもエラーにならない)
"abcdefg".slice(3, 10)
//=> defg
// 指定したSuffixだったら削除する
"test.txt".stripSuffix(".txt")
//=> text
また、JavaではStringのstaticメソッドとして実装されていたformatメソッドも使えるようになっています。なので、こんな書き方も可能です。
//【Scala】
// 3桁でゼロ埋め
"%03d".format(3)
//=> 003
// カンマ区切り
"%,d" format 65536
//=> 65,536
// 日付フォーマット
"%tY/%<tm/%<td" format new Date()
//=> 20111210
formatのお陰でちょっとした数値や日付の整形がけっこう楽に書けます。
文字列の数値変換とかも楽になります。Javaの場合はこんな風に書いたと思います。
【Java】
int i = Integer.parseInt("100");
double d = Double.parseDouble("100.0");
Scalaの場合は文字列に変換用のメソッドが内蔵されているので、以下のように書けます。
【Scala】
val i = "100".toInt
val d = "100.0".toDouble
また、数値の機能もちょっと拡張されています。
【Scala】
// Int -" Doubleへの型変換
10.toDouble
//=> 10.0
// 絶対値取得
-27.abs
//=> 27
// floorとかceilも使えます
3.14.floor
//=> 3.0
ScalaはJavaの機能がそのまま使えてその上に機能を載せているので、やれることはけっこう豊富です。
というわけで今回はJavaプログラマの皆さんが不足を感じていそうな部分(というか私自身が不足を感じていた部分)を中心に取り扱ってみました。
1つくらいは「これ使いたいなぁ」という機能が見つかったでしょうか。
他にもいろいろ紹介したい機能はあるのですが(パターンマッチとか、暗黙の型変換とか、Processとか、XMLの扱いとか、importの記述とか、ソートとかが書きやすいとか)長くなってきたので今回はこの辺で。