Scala で遊んでみました
最近よく名前を聞くようになった Scala という言語で遊んでみました。
Scala の特徴
Scala (スカラ、Scalable Language) はオブジェクト指向言語と関数型言語の特徴を統合したマルチパラダイムのプログラミング言語である。
wikipedia
様々な特徴があるようなのですが、個人的に気になったのが、
- 型推論を用いた静的型付けの言語である
- パターンマッチが使える
- 遅延評価がある
- 構文解析のためのパーサーコンビネータが標準ライブラリにある
- Java や .NET Framework のライブラリが使える
- 標準ライブラリに Actor と呼ばれる Erlang のような文法の軽量プロセスがある
です。 理由としては、前半 4 つは Haskell の特徴でもあるので、Haskell 使いとしては見逃せません。 Java の Swing は使い慣れているので、GUI を作るのが楽そうだし、Erlang のような並列処理については前々から使ってみようと思っていたからです。
チュートリアルに沿って
手始めに、A Scala Tutorial for Java programmers (和訳) というチュートリアルを読み進めていきました。 Scala の概要を説明するのが目的だそうです。 (チュートリアルが古いのか、println 関数は、Console.println でないとコンパイルが通りませんでした。)
全てはオブジェクト
Scala では全てのもの (関数や数など) はオブジェクトとして定義されています。 そのため、関数型言語のように、関数を別の関数の引数にしたり、関数を返す関数を定義することが自然にできます。 例えば、以下のプログラム
1 + 2 * 3 / x |
1.+(2.*(3./(x))) |
と同じ意味で、数字オブジェクトに四則演算のオペレータが定義されていることが窺えます。
上記のような四則演算以外でも、オブジェクトのメソッドの引数が一意に定まる場合、「.」とカッコを省略することができるようです。 例えば、以下のような書き方ができます。
import javax.swing._ //* の代わりに _ を使うようです
import java.awt._
object SwingTest{
def main(args: Array[String]) = {
var frame = new JFrame
frame setDefaultCloseOperation (JFrame EXIT_ON_CLOSE)
frame setSize new Dimension (640, 480)
frame setVisible true
}
} |
Java の Swing を呼び出して、640x480 のウインドウを出す例ですが、main 内の frame のメソッド呼出しが Java のそれとはかなり違います。 さらに、setDefaultCloseOperation と setSize はともに 1 引数ですが、前者にはカッコがあって、後者はありません。 こうしないとコンパイルが通らないからです。 ためしに、setDefaultCloseOperation の引数を 3 * 2 にしてみたら、カッコなしでもコンパイルが通りました。 どうやら、演算子の優先順位と左結合、右結合によってうまくいったりいかなかったりするようです。
無名関数
関数型言語のいうところのλ式と似たようなものである無名関数が使えます。
import javax.swing._
import java.awt._
import java.awt.event._
class ActionListenerProc(f: ActionEvent => Unit) extends ActionListener{
override def actionPerformed(ev: ActionEvent) = { f(ev) }
}
object SwingTest2{
def createUI(pane: JPanel) = {
var but = new JButton
// 無名関数を使っている
but addActionListener (new ActionListenerProc (ev => Console.println(ev)))
pane add but
}
def main(args: Array[String]) = {
var frame = new JFrame
frame setDefaultCloseOperation (JFrame EXIT_ON_CLOSE)
frame setSize new Dimension (640, 480)
createUI ((frame getContentPane).asInstanceOf[JPanel])
frame setVisible true
}
} |
無名関数は、({引数} => {式}) という形で宣言することができます。 複数の引数がある場合、({引数1} => {引数2} => ... => {式}) という風に宣言できます。 上記のプログラムは、無名関数を使って、ボタンが押されたら ActionEvent の内容を出力します。
パターンマッチ
パターンマッチの例のため、Haskell でいうところの Maybe を定義してみました。 Maybe は計算が失敗する可能性のある時に返すものです。 まったく同じものが、Scala では Option というオブジェクトとして存在します。
//Maybe の定義
abstract class Maybe[A]
case class Just[A](v: A) extends Maybe[A]{
val value = v
}
case class Nothing[A] extends Maybe[A]
object MaybeTest{
def find[A](l:List[A], key:A): Maybe[A] = {
for(x <- l) if (x == key) return Just(x)
return Nothing[A]
}
def search[A](db:List[A], key:A) = {
// Maybe[A] を使ったパターンマッチ
find(db, key) match {
case Just(v) =>
Console.println(v + ": found")
case Nothing() =>
Console.println(key + ": not found")
}
}
def main(args: Array[String]) = {
val db = List("Narita", "Saito", "Matsuura", "Tayama")
search(db, "Narita")
search(db, "Nihei")
}
} |
パターンマッチできるクラスは、case class で定義する必要があります。 基底クラスを一つ作り、それを継承した case class を複数作る、というのが一般的です。 上記の例では、抽象クラス Maybe[A] (ジェネリック型。A は型パラメータ) を継承して、パターンマッチ可能な Just[A](v: A) と Nothing[A] というクラスを定義しています。
search メソッドの中で match {} という構文を用いて、find メソッドの返り値に対してパターンマッチを行っています。 それぞれのパターンの => の直前に if 文を書いて制限を加えることもできます。
型推論
型推論を用いて、型を明記することなく静的に型をチェックできるはずなのですが、メソッドの引数には型を付けなくてはならないようで、あまりありがたみがありません。
trait Show{
def show(): String
}
class Hoge extends Show{
override def show() = "Hoge"
}
object InfTest{
//a show を使っているのだから、Show くらい推論してほしい
def print(a: Show) = { Console println (a show) }
def main(args: Array[String]) = {
print(new Hoge)
}
} |
trait を用いて、Mixin 専用のクラスを作ることができます。 この例では、文字列を返すメソッド show を持つ Show クラスを定義します。 それを Hoge が Mixin して、show メソッドを定義します。 そして、InfTest の print メソッドは、引数 a の show メソッドを呼び、それをコンソールに出力します。 ここで、この print メソッドの引数は Show クラスと推論して欲しいのですが、引数 a の型を明記しないとコンパイルエラーがでます。 メソッドのオーバーロードやクラススコープなどによって、同名のメソッドが存在しうるため、型の推論ができないのではないかと思われます。
所感
チュートリアルを読んだ程度ですが、大雑把に Scala の使い方が分かりました。 構文にはなんだか曖昧な部分が多いように感じられましたが「しばらく書いていれば何となく分かる」感じの言語でした。
マルチパラダイム言語と呼ばれていますが、どちらかというとまだオブジェクト指向よりな部分が多いと思います。 Java よりオブジェクトの種類が細分化され、singleton なら object, Mixin 専用なら trait, パターンマッチ可にする case, 従来のクラスと同じ class などがあるようです。 継承の仕方や型の表現に対してもいくつかの表現があり、覚えなければならない規則はなかなか多そうです。
遅延評価、パーサコンビネータ、Erlang ライクな並列処理についても、もう少し遊んでみてから書こうと思います。
担当:齋藤 (まだ Haskell の方が好き)

コメントを投稿