暫くぶりに投稿します。
近頃は、時間があればScalaを勉強しています。
ScalaはJavaにはない興味深い機能が多くあり、それらの内の幾つかを コードを見ながら、ここで、解説していきたいと思います。
また勉強の過程で知ったspecsという検証用のフレームワークがなかなか おもしろかったので、そちらも紹介したいと思います。
Scala
Scalaは特徴の多いプログラミング言語です。関数を変数として保持できたり、クロージャ、多重継承やパターンマッチ、 Erlangのような並列処理機構などなど数多くの機能を備えています。
学習の敷居はそれなりに高い(私は苦戦中...)のですが、言語の表現力の高さや、 パフォーマンス、 Javaライブラリを利用可能など、魅力に富んでいます。
個人的には構文解析のパーサが気になります。(TODO:後で勉強。)
リンク
- Scala (英語) : 本家
- Scala By Example (pdf,英語) : Scalaの特徴を体系的に解説している ScalaByExample
- Scala By Example 和訳 : 和訳プロジェクト
specs
specsはScalaでJUnitのようにテストを実施するためのフレームワークです。specsは仕様を書くような感覚で検証コードを書くことをその設計思想としている ようです。(BDD: Behavior Driven Development )
人に優しい、プログラムの読みやすさや簡潔さにこだわりを感じるフレームワークです。
リンク
Scala - 電話番号っぽい?
Scalaの構文を勉強しながら、ちょっとしたコードを書きました。以下は、文字列が電話番号っぽいかを判定します。
object PhoneNumber {
val phoneNumPtn = """^(\d{2,4}+)\-?(\d{2,4}+)\-?(\d{2,4}+)$""".r
// 電話番号ぽっい番号の場合はtrueを返す
def isValid(phoneNumber:String):Boolean = {
phoneNumber match {
case phoneNumPtn(s1:String, s2:String, s3:String) => {
println( "valid : " + s1 + s2 + s3 );
return true;
}
case _ => return false
}
}
}
※ ここでは、Scalaのバージョン 2.7.1.final を使用しています。コードの解説 / バラバラと
object PhoneNumber {
...
}
objectで定義するとPhoneNumberはシングルトンとしてインスタンス化されます。class PhoneNumber も別途定義できます。
val phoneNumPtn = """^(\d{2,4}+)\-?(\d{2,4}+)\-?(\d{2,4}+)$""".r
これは正規表現パターンを定義します。valは変更不可の値を定義します。Javaのfinalのようなものです。 ( 変更可能な値は var で定義します。 )
"fugafuga".rの様に文字列に.r を付与すると、文字列が正規表現として解釈され、scala.util.matching.Regex 型のインスタンスが作られます。
また、""" で文字列を囲うと、いわゆるヒアドキュメントとして扱われます。
ヒアドキュメントは、複数行に渡る文字列を定義するときなどに使用します。ここでは、\マークをエスケープせずに記述するために使用しています。
def isValid(phoneNumber:String):Boolean = { ... }
def はメソッドを定義するときに使用します。このメソッドはString型の引数を1つ受け取ります。
末尾の :Boolean はこのメソッドが Boolean型 を返すことを意味します。
phoneNumber match {
case phoneNumPtn(s1:String, s2:String, s3:String) => {
println( "valid : " + s1 + s2 + s3 );
return true;
}
case _ => return false
}
match { case xxx => { ... } case yyy => { ... } } はパターンマッチングの構文です。case の条件を上から順に評価し、条件にマッチした場合、=> の右辺のブロックが実行されます。phoneNumPtn(s1:String, s2:String, s3:String) の部分では、phoneNumPtnで定義される正規表現にマッチする場合、正規表現の()で囲>われた値に相当する文字列をs1,s2,s3にセットします。
この例で文字列 11-22-33 でマッチングさせると s1="11" s2="22" s3="33" になります。
case _ は必ずマッチします。
specsで検証
specsを使って先述のコードを検証するコードを用意しました。
import org.specs._
object PhoneNumberSpec extends Specification {
"正しい電話番号である" in {
PhoneNumber.isValid("0120-000-000") mustBe true
PhoneNumber.isValid("03-0000-0000") mustBe true
PhoneNumber.isValid("090-0000-0000") mustBe true
PhoneNumber.isValid("110") mustBe true // failure
}
}
※ ここでは、specsのバージョン 1.2.9 を使用しています。コードにある通り「正しい電話番号である」ことを検証します。
specsは簡潔に記述すること、何を行うのかを明瞭におくことを易しくしてくれます。
検証処理の流れ
1. 暗黙的型変換 - implicit conversion
実際に処理がどのように呼び出されるかを確認します。上記のコードでは主な検証処理を以下の様な形式で実行しています。
"文字列" in { ..処理.. }
このコードには若干トリックが含まれています。タネは、継承元のクラスにあります。上記のクラスの親の一つにSpecificationStructureクラスがあり、そこに以下のメソッドが定義がされています。
implicit def forExample(desc: String): Example = { ... }
このコードによって、文字列は暗黙的にExample型のインスタンスに変換されます。in メソッドはString型に定義はなく、Example型に定義されているため、Exapmple型へ変換されるのです。
変換処理が上述のforExampleメソッドです。(メソッドの名前は何でも構いません。引数と返却値の型によって呼び出し先が決定されます。)
これは、Scalaの implicit conversion (暗黙の型変換) と呼ばれる機能です。
2. 省略記法、名前渡し
次に in の部分ですがこれは、in というメソッドを呼び出しています。Scalaでは、メソッド呼び出し時にドットや引数の括弧などを省略して記述することができます。
元のコードの省略記法を利用せずに書き直すと、以下の様になります。
forExample("正しい電話番号である").in( {
PhoneNumber.isValid("0120-000-000").mustBe(true)
PhoneNumber.isValid("03-0000-0000").mustBe(true)
PhoneNumber.isValid("090-0000-0000").mustBe(true)
PhoneNumber.isValid("110").mustBe(true) // failure
} )
(どちらの書き方が良いかは好みにもよりますね。)inメソッドは、Any型の値を1つ「名前渡し(call-by-name)」で受け取って、Example型を返すメソッドです。
メソッドはExampleクラスに定義されています。
def in (test: => Any): Example = { .... }
引数の型を 「 => Any 」 の様に => を付与して宣言することで、名前渡しにすることができます。名前渡しの場合、引数が参照される度に、値が評価されます。
今回の例では、{}で囲まれたブロックを渡しており、ブロックの中身が in メソッドの中で評価されます。
結果として、ブロックの中味が実行されて、検証の処理が行われます。
3. 再び implicit conversion
ブロックの中では mustBe メソッドを呼び出しています。これも implicit conversionによって、実際にはAssertクラスに変換されて、mustBe メソッドが 呼び出されます。
このimplicit conversionは、Specificationの親クラスの一つである、AsserFactoryに定義されています。
implicit def theValue[A](value: =>A) = {
addAssertion
new Assert[A](value)
}
この処理を明示的にして書き直すとこのようになります。
forExample("正しい電話番号である").in( {
theValue( PhoneNumber.isValid("0120-000-000") ).mustBe(true)
theValue( PhoneNumber.isValid("03-0000-0000") ).mustBe(true)
theValue( PhoneNumber.isValid("090-0000-0000") ).mustBe(true)
theValue( PhoneNumber.isValid("110") ).mustBe(true) // failure
} )
明示的にすることで、処理の流れは分りやすくなりましたが、
コードの意図、即ち何をしようとしているのかが若干分りづらくなったように感じます。
Scalaでコードを書くときには、この辺りのトレードオフを意識しておくとよいかもしれません。
実行結果
> fsc PhoneNumber*.scala
> scala PhoneNumberSpec
Specification "PhoneNumberSpec"
specifies
valid : 0120000000
valid : 0300000000
valid : 09000000000
x 正しい電話番号である
'false' is not the same as 'true' (PhoneNumberSpec.scala:9)
Total for specification "PhoneNumberSpec":
Finished in 0 second, 113 ms
1 example, 4 assertions, 1 failure, 0 error
おわりに
まだ深くは見ていませんが、specsのAPIは良く考えられていて、実用性のある機能が 利用しやすい形で提供されている気がします。こういったAPIを見て、簡潔に物事を表現する手段として、 Scalaイケテルヤモ? と思っている今日この頃です。では。