はじめに
Java仮想マシン(以下JVM)上で動くオブジェクト指向+関数型言語として、Scala(スカラ)が最近注目を集めています。
Scalaで構築されたWebアプリケーションフレームワークはいくつかありますが、 本稿ではその中で比較的歴史のある(といっても2年程度ですが) フレームワークである、Lift(リフト)を紹介したいと思います。
対象読者
- Javaは知っているが、Scalaも学んでみたいと思っている方
- ScalaでのWebアプリケーション開発に興味がある方
必要な環境
最低動作環境
- J2SE 5.0 JDK
推奨環境
- J2SE 6.0 JDK
- Apache Maven 2.10
- Scala 2.7.5
Scalaとは
Scalaは、JVM上で動作するオブジェクト指向+関数型言語で、Javaとほぼ完全な相互利用が可能な言語です。作者であるMartin Odersky教授はJavac開発の貢献者であるとともに、Java5 Genericsの仕様策定にも参加しています。
Scalaの特徴を簡単に列挙します。
1. JVM上で動作
Scalaのソースコードをコンパイルすると、classファイルが生成されます。このclassファイルとScalaのランタイムjarをJVM上に配置することでScalaは動作します。
JVM上で動作すると言うことは、JVMのスケーラビリティを適用できるということです。
2. Javaとの相互利用が可能
ScalaからJavaのクラスを利用できます。これは、豊富に用意されているJavaのライブラリをScalaでも利用できるということです。逆にJavaからScalaのクラスを利用することもできます。
3. インタプリタ
Scalaには、入力したコードを即座に評価して結果を確認出来る対話型インタプリタが付属しています。LL言語のように、ちょっとコードを書いてみて動作を確認するといったことがすぐにできるようになっています。
4. オブジェクト指向+関数型言語
Scalaには、Javaでいうところのプリミティブ型がありません。Rubyのように、IntもDoubleも全てオブジェクトなのです。
Scalaはオブジェクト指向の言語ですが、加えて関数型言語としての側面も持っています。例えば、高階関数はもちろん利用できますし(引数や戻り値に関数を指定できる)、関数型言語でよく利用される末尾再帰(関数の最後の処理で自分自身を再帰で呼び出すこと)はコンパイル時に最適化されます。
5. 静的型付け言語と型推論
Scalaは静的型付け言語です。互換性のない型への代入はコンパイル時にエラーになります。しかし、型推論によって型の記述を省略することができます。変数の宣言時に自明な型は、わざわざ型を指定しなくともコンパイラが推論してチェックしてくれるのです。
リスト1は、対話型インタプリタを利用して実行した型推論の例です。*1のように変数の型を省略して宣言できます。また、*2では、*1で宣言した変数numにString型を代入してエラーになっている様子が分かると思います。
scala> var num:Int = 1 num: Int = 1 Int型の変数numを宣言 scala> var num = 1 *1 num: Int = 1 変数numの型を省略して宣言。型推論によってnumの型はInt型になる scala> num = "foo" *2 <console>:5: error: type mismatch; found : java.lang.String("foo") required: Int num = "foo" ^ Int型の変数numにString型を代入しているので、エラーとなる
5. ファーストクラスオブジェクトな関数
Scalaの関数はファーストクラスオブジェクトです。ファーストクラスオブジェクトとは、変数への代入や関数の戻り値として扱える、いわば値としての性質を持つオブジェクトです。Scalaの関数がファーストクラスオブジェクトということは、Javaのメソッドに相当する関数を関数オブジェクトとして、変数に代入したり、関数の戻り値として関数を返したりと、動的に扱うことが可能です。
例えば高階関数として、関数を生成して返す関数を定義したり、関数の引数に関数オブジェクトを渡して呼び出し先で利用したりと、柔軟なプログラミングが可能です。
リスト2では、isEvenという関数をfuncという変数に代入しています。
scala> def isEven(i:Int) = i % 2 == 0 isEven: (Int)Boolean scala> val func = isEven _ func: (Int) => Boolean = <function>
6. caseクラス、パターンマッチング
Scalaでは、caseクラスとパターンマッチングという機能を利用することで、複雑な条件分岐を簡潔に記述することができます。ものすごくおおざっぱにいうと、オブジェクトの構造に対してパターンを適用して分岐できます。この言語仕様により、Scalaではif文を記述することはあまり多くありません。
リスト3では、List型の変数の内容についてパターンマッチングを行う例です。
scala> def test[T](list:List[T]) = list match { case 1::xs => print("start 1") 先頭が1から始まるListの場合 case 2::xs => print("start 2") 先頭が2から始まるListの場合 case "Test"::xs => print("start Test") 先頭が"Test"から始まるListの場合 case _ => print("default") どのパターンでも無い場合 } test: [T](List[T])Unit scala> test(List(1,2)) start 1 先頭が1から始まるListを渡した結果 scala> test(List(2,3)) start 2 先頭が2から始まるListを渡した結果 scala> test(List("Test","aaa")) start Test 先頭が"Test"から始まるListを渡した結果 scala> test(List("bbb","ccc")) default どのパターンにも当てはまらないListを渡した結果
7. XMLリテラル
XMLをリテラルとして、直接プログラムの中に埋め込むことが可能です。記述されたXMLはオブジェクトとして扱うことができますし、XMLの中にScalaの式を埋め込むことも可能です。前述したパターンマッチングと組み合わせることで、ScalaでのXMLの扱いは非常に簡単になっています。
LiftのViewは、このXMLリテラルをうまく利用しています。
リスト4は、XMLリテラルの例です。*1では、ダブルクォートやシングルクォートを書かずにXMLそのものを変数に代入しています。
*2では、*1で代入したXMLから、bazタグを取り出しています。
scala> val xml = <foo><bar>aaa</bar><bar>bbb</bar><baz>ccc</baz></foo> *1 xml: scala.xml.Elem = <foo><bar>aaa</bar><bar>bbb</bar><baz>ccc</baz></foo> scala> xml \ "baz" *2 res1: scala.xml.NodeSeq = <baz>ccc</baz>
8. traitによるMix-In
Scalaでは、traitを利用した多重継承(Mix-In)が可能です。traitは、Javaのインターフェースのような機能で、一つクラスが複数のtraitを継承できます。しかし、Javaのインターフェースと決定的に異なる点は、実装を定義できるということです。
traitを利用することにより、実装を再利用して既存のクラスに新しい機能を付加することができるようになります。
機能的にはRubyのモジュールとほぼ同じですが、traitの型もコンパイル時にチェックされますので、モジュールよりも型セーフに利用することができます。
他にも、暗黙の型変換を行うimplicit conversion、クラスの型によらず特定のメソッドをもっているかで呼び出しを行えるstructual typing、非同期処理を行うactorライブラリなど、ここでは紹介しきれないほど多くの特徴を持っています。
Javaを普段利用されている方は、LL言語のような簡便な記述やクロージャをうらやましく思ったことはないでしょうか? Scalaは、そんなJavaプログラマに、シンプルかつパワフルなプログラミングを提供します。
また、LL言語に慣れ親しんだ方は、それまでの軽量な開発スタイルはそのままに、静的型付け言語と型推論によるタイプセーフなプログラミングモデルを手に入れることができます。