kmizuの日記

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

初学者向けの 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()を使う事が有用な事がありますが、それはまた今度。

Sonatype Nexus repositoryにライブラリをアップロードしようとしてはまった話

昨日、Scala 2.11.0がリリースされたということで、自分(たち)がメンテしているライブラリnscala-timeScala 2.11.0対応版をSonatype Nexus Repository(これ、正式名称は何なのだろうか未だに悩んでいる)にアップロードしようとしていたのだが、

こんな画像

な感じで何故か301が返ってきてはまっていた。

原因はわかってみると簡単。どうも、いつのまにかSonatypeがhttps://で始まるURLしか受け付けないようになっていたのに、アップロード先のURLがhttp://のままなのであった。

修正は簡単で、

publishTo <<= version { v =>
  val nexus = "http://oss.sonatype.org/"
  if (v.endsWith("-SNAPSHOT"))
    Some("snapshots" at nexus + "content/repositories/snapshots")
  else
    Some("releases" at nexus + "service/local/staging/deploy/maven2")
}

となっていたのを

publishTo <<= version { v =>
  val nexus = "https://oss.sonatype.org/"
  if (v.endsWith("-SNAPSHOT"))
    Some("snapshots" at nexus + "content/repositories/snapshots")
  else
    Some("releases" at nexus + "service/local/staging/deploy/maven2")
}

にするだけ。

名前を簡単に変えられることのありがたさ

何を当たり前のことを、と言われそうだが、最近の静的型付き言語のIDEには、「名前変更」のリファクタリングを自動的に行ってくれる機能があり(リフレクションなどIDEが追跡可能な範囲を超える場合を除いて)、後から簡単に名前を変更することができる。このような機能は動的型付き言語用のIDEにもみられるものではあるが、IDEが追跡できる範囲がより限定的であるため、結局、IDEによる変更が正しいかどうかは目視に頼らざるを得ない。

静的型付き言語であっても、文字列を使ってリフレクションAPIを使用する場合など注意すべき点はあるのだが、その範囲が限定的であるため、名前の変更を安心して行うことができる。名前というのは後になってから変更したくなるものの一つであるため、これは静的型付き言語用の高度なIDE(Eclipse, NetBeans, IntelliJ IDEA等)の利点という事ができるだろう。

特にプロジェクトの初期に恥ずかしいtypoあるいは勘違いをしてしまい、その名前をあちこちで使った後に間違いに気づいた時にはとてもありがたい(動的型付き言語のIDEでこのテの、全体に広まってしまったtypoを直すのは結構大変である。たとえば、筆者はRubyMineというRuby on RailsIDE( このIDE自体は非常によくできている)で、Ruby on Railsで作られたアプリケーションに対してこのような作業を行った事があるが、なかなか大変な作業であった…)。