kmizuの日記

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

初学者向けの 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を返さないと確信できるケースではその限りではありません)。

それでは、また。

Scala 2.11.0で標準ライブラリから分離されたライブラリのjarファイル

今日のScala勉強会第124回 in 本郷で話題に出てたことの備忘録。Scala 2.11で、scala.util.parsing.combinatorscala.xmlscala.util.continuationsが標準のライブラリから分離されたのだが、これを今まで通りに使うにはどうすればいいのか。実は、分離されたライブラリの内、前者二つは以下に

https://oss.sonatype.org/content/repositories/releases/org/scala-lang/modules/

continuations

https://oss.sonatype.org/content/repositories/releases/org/scala-lang/plugins/

に入っているので、これらを今まで通りに使うにはsbtの設定ファイルに

libraryDependencies ++= Seq(
  "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.1",
  "org.scala-lang.modules" %% "scala-xml" %  "1.0.1",
  "org.scala-lang.plugins" %% "scala-continuations-library" % "1.0.1"
)

という記述を追加してやればよい。

追記:コメントにある通り、継続(continuations)プラグインを使うには、

autoCompilerPlugins := true

addCompilerPlugin("org.scala-lang.plugins" % "scala-continuations-plugin_2.11.0" % "1.0.1")

scalacOptions += "-P:continuations:enable"

という記述が追加で必要になる。今までもcontinuation pluginを使うには必要な記述であったが、2.11.0ではcontinuation pluginの置き場所が異なっている点に注意。

初学者向けの Scala Tips (1) - Option#map()を使おう

唐突に始まったScala Tipsコーナー、初学者が陥りがちなScalaのコードパターンを例にして、より良い方法を解説していきます。第一回はOptionクラスのmap()メソッドを使おうというものです。知ってる人には今更ですが、Option#map(f)は、レシーバーの値がSome(v)の場合は、fvを渡した結果のSome(f(v))が、Noneの場合はNoneが返ります。したがって、パターンマッチを使った以下のようなコードは

val result = exp1 match {
  case Some(v1) =>
    val vx = //expression using v1
    Some(vx)
  case None =>
    None
}

常に以下のような形に書き換えることができます。

val result = exp1.map{v1 =>
  val vx = //expression using v1
  vx
}

より一般的なコードでは、flatMap()を使う事が有用な事がありますが、それはまた今度。