kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

初学者向けの 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への変換

Scalanullを使っていいのは小学生までだよねー、というのは冗談ですが、Scala文化圏ではnullを使わないのが原則です(いくつか例外はありますが)。代わりに出てくるのが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を返さないと確信できるケースではその限りではありません)。

それでは、また。