今年はドイツのベルリンで6月16日〜6月18日にかけて開催されます。去年よりも発表のバリエーションが豊かで今からどのセッションを見に行こうか と考えると非常に楽しみです。
初学者向けの Scala Tips (5) - パターンマッチと無名関数の組み合わせを簡潔に書く
Scala初学者の方が書くコードには、しばしば以下のようなものが散見されます。
list.map {x => x match { case A => case B => case _ => } }
無名関数を作るための構文である{x => ...}
とexp match { case ... }
を別々に考えるとこのようなコードになるのは無理はありません。しかし、これはもっと簡潔に書くことができます。改良したのが以下のコードです。
list.map { case A => case B => case _ => }
前者の書き方に比べて断然簡潔ですね。特別な事情がない限り、無名関数の引数を即座にパターンマッチする必要がある場合、後者の書き方を使いましょう。
ちなみに、この書き方を教えると結構びっくりされる方が多いようで、この構文はPartialFunction
のためのものだと思っていた、ということのようです。確かに、この{ case .... }
という構文は、PartialFunction[-A, +B]
型のオブジェクトを作るためにも使われます。ただし、それが適用されるのは、左辺がPartialFunction
型を要求しているときのみ、つまり
- (代入/初期化)の左辺がPartialFunctionである
- メソッドの仮引数がPartialFunctionである
時に限られます。それ以外の場合、
list.map { case A => /* A */ case B => /* B */ case _ => /* _ */ }
というコードは以下のコードと同じように解釈されます。
list.map {x => x match { case A => /* A */ case B => /* B */ case _ => /* _ */ } }
ちなみに、IntelliJ IDEAは前者の形のコードを検出して、後者の形に変換することを提案する機能を持っているのですが、その名前が「Convert match statement to partial function」という名前です。思わず、誤解を招く名前を付けるなよ!とつっこみたくなりました。
初学者向けの Scala Tips (4) - 可変長引数を取る関数にSeq[T]を渡す
TipsというよりFAQの部類ですが、ときどきつまづく方がいるようなので。
Scalaで以下のような可変長引数を取る関数printAll
を定義すると、
def printAll(ss: String*): Unit = { ss.foreach(println) }
printAll("BAZ", "FOO", "BAR") //BAZ //FOO //BAR
のように任意個のStringを渡すことができます。問題は、既に
val fooBar = Seq("FOO", "BAR")
のように既にSeq
型のオブジェクトが作られている場合。
fooBar
をそのまま引数として渡し、printAll(fooBar)
としてしまうと、
(snip) error: type mismatch; found : Seq[String] required: String printAll(fooBar)
というエラーが出てしまいます。これを解決するには、
printAll(fooBar:_*) //FOO //BAR
のようにすればOKです。このようにすれば、scalacがfooBarを可変長引数に一つずつ渡したものとして扱ってくれます。
初学者向けでない Scalaバッドノウハウ(1) - traitの初期化時にManifest型の値を取得する
ネタが尽きたわけではないが、Manifest絡みで嫌な現象に遭遇したのでメモと回避方法(現在はManifest
型をつかうべきではないがそれはおいておく)。
まず、問題として、次のようなコードを書くと実行時例外(unknown error
)が発生する(Scala 2.10.4)。
trait M[T] { implicit val m: Manifest[T] } case class Point(x: Int, y: Int) object Main extends M[Point] { implicit val m: Manifest[Point] = manifest[Point] def main(args: Array[String]) { json4sObj.extract[Point] } }
このようになる原因は、traitでの初期化でmanifest
メソッドを呼び出すとnull
が返ってくることにあるようだ(manifest
メソッドはscalacで特別な扱いを受けていることに関連していると思われる)。それならと、manifest
呼び出しのタイミングを遅らせるように
trait M[T] { implicit def m: Manifest[T] } case class Point(x: Int, y: Int) object Main extends M[Point] { implicit def m: Manifest[Point] = manifest[Point] def main(args: Array[String]) { json4sObj.extract[Point] } }
としてみると、今度は
at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8) at Main$.m(M.scala:8)
というStackOverflowErrorが発生する。これの回避策だが、
implicit def m
の部分を
implicit def m()
に書き換えればOK。つまり、最終的なコードは以下のようになる。
trait M[T] { implicit def m(): Manifest[T] } case class Point(x: Int, y: Int) object Main extends M[Point] { implicit def m(): Manifest[Point] = manifest[Point] def main(args: Array[String]) { json4sObj.extract[Point] } }
これでうまく行く理由、というより、一つ前のコードがうまく行かなかった理由が釈然としないのだが…(コンパイラが暗黙に想定しているimplicitなManifest[T]
を上書きしてしまったせいではないかと推測している)
初学者向けの Scala Tips (4) - パターンマッチのcase節には複数の式が書ける
ときどきみかけるコードなのですが、パターンマッチを使ったコードで、次のようなものがあります。
exp match { case A => { expA1 expA2 ... } case B => { expB1 exbB2 ... } }
Scalaでは、case節に複数の式を続けて書くことができるのでこのようなブレースは不要です。
exp match { case A => expA1 expA2 ... case B => expB1 expB2 ... }
としてしまいましょう。
初学者向けの Scala Tips (3) - 型に対するマッチより構造に対するマッチ
皆さんご存知の通り、Scalaにはパターンマッチという機能があります。この機能、しばしば型に対するswitch-caseとして使われていることがあるようです。
たとえば、
sealed trait class Value case class Hoge(value: Int) extends Value case class Foo(value: String) extends Value case class Bar(value: List[String]) extends Value
というコードがあったとき、しばしば
value match { case hoge:Hoge => doHoge(hoge.value) case foo:Foo => doFoo(foo.value) case bar:Bar => doBar(bar.value) }
のような形で使われることがあります。このような使い方は場合によって必要なのですが、上記のようなコードでは全く不要です。以下のように機械的に書き換えてしまいましょう。
value match { case Hoge(value) => doHoge(value) case Foo(value) => doFoo(value) case Bar(value) => doBar(value) }
今回のようなケースではさほど差はあらわれませんが、より複雑なデータ構造をパターンマッチで分解するとき、型に対するswitch-caseを使うのとそうでないのとでは大きな差が出てきます。型に対するswitch-caseはあくまで必要悪です。使わずに済むならそれにこしたことはないということをよく覚えておいてください。
ではでは、また。
初学者向けの Scala Tips (2) - nullからOptionへの変換
Scalaでnull
を使っていいのは小学生までだよねー、というのは冗談ですが、Scala文化圏ではOption
型です。しかし、Scalaでは困ったことにnull
を返すJavaのメソッドを呼び出さなければいけない場面がたくさんあります。
たとえば、以下のようなコードを書かなければいけない場面にはしばしば遭遇します。
val javaMap: java.util.Map[String, String] = javaObject.getMapping() val value: String = javaMap.get(key) // 型注釈はあえて付けている if(value != null) { ... } else { ... }
さて、ここで困るのが、javaMap.get(key)
はkey
に対応する値が入っていない場合null
を返すため、value
にはnull
が入る可能性があるという点です。こんなときは、Option.apply
の出番です。上のコードは次のように書き換えることができます。
val javaMap: java.util.Map[String, String] = javaObject.getMapping() val value: Option[String] = Option(javaMap.get(key)) // 型注釈はあえて付けている value match { case Some(value) ... case None => ... }
Scalaからnullを返すかもしれないJavaのメソッドを呼び出すときは、このようにOption.apply
で浄化することをこころがけましょう(もちろん、対象とするJavaのメソッドがnull
を返さないと確信できるケースではその限りではありません)。
それでは、また。