追記 2
まさに@xuwei_kさんからコメントしていただいた通りで、2点勘違いしていました。
- 型としてmatchする 可能性がある ことと、値が実際にmatchするのを勘違い
- コンパイルエラーと実行時のエラーを勘違い
同じようなコードをREPLじゃなくてファイルで書いたとしても、
import scala.util.{Failure, Success, Try}
object MatchTest {
def main(args: Array[String]) {
val e = Try {
Option(null)
} match {
case Success(Some(n)) => Right(n.toLowerCase)
case Success(None) => Left("returned None")
case Failure(e) => Left(e.getMessage)
}
println(e)
}
/** [値, null, 例外]という3種類の戻りがあるメソッド
* 実際にはJavaのライブラリの呼び出し */
def doorSecurity(name: String): String = {
name match {
case "Bob" => null
case "Dave" => throw new RuntimeException("Dave is not allowed")
case n => n
}
}
}
コンパイル時にエラーを出してくれます。
$ scalac MatchTest.scala
MatchTest.scala:9: error: value toLowerCase is not a member of Null
case Success(Some(n)) => Right(n.toLowerCase)
^
one error found
Try { Option(null) }
と書いたら戻りの型はTry[Option[Null]]
になるというのは理解できます。
ただ、以下のように書いたら、Success(None)
がmatchするのはなぜか?まだ腑に落ちていません。まだ考えが足りない。
import scala.util.{Try, Success, Failure}
object TryOptionTest {
def main(args: Array[String]): Unit = {
val people = List("Alice", "Bob", "Carol", "Dave")
people map entrance foreach {
case Right(n) => println(s"Hello $n")
case Left(m) => println(m)
}
}
def entrance(name: String): Either[String, String] = {
Try {
Option(doorSecurity(name))
} match {
case Success(Some(n)) => Right(n)
case Success(None) => Left("do not pass null")
case Failure(e) => Left(e.getMessage)
}
}
/** [値, null, 例外]という3種類の戻りがあるメソッド
* 実際にはJavaのライブラリの呼び出し */
def doorSecurity(name: String): String = {
name match {
case "Bob" => null
case "Dave" => throw new RuntimeException("Dave is not allowed")
case n => n
}
}
}
コンパイル時には、Try{ Option(doorSecurity(name)) }
の戻り値の型がTry[Option[String]]
と推論されて、実行時にdoorSecurity
がnull
を返したときには、実際にSuccess(None)
になるから、という感じ、でしょうか。
旧タイトル: Try(None)からのパターンマッチがSuccess(Some(n))にマッチしてしまう?というのはなかった
追記: 以下の挙動はsbt consoleだけだった。普通にファイル作ってやれば期待通り動いた。
Scala version: 2.10.2
import scala.util.{Try, Success, Failure}
object TryOptionTest {
def main(args: Array[String]): Unit = {
val l1 = List(1, "abc", null)
l1 foreach f1
}
def f1(v: Any): Unit = {
Try {
Option(v)
} match {
case Success(Some(n)) => println(2)
case Success(None) => println(1)
case Failure(e) => println(0)
}
}
}
$ scalac TryOptionTest.scala
$ scala TryOptionTest
2
2
1
よかったです。
例えばこんなコードを書いたときに、Success(None)
にマッチするのを期待してたけど、Success(Some(n))
にマッチしてしまう。
scala> Try {
| Option(null) // 本当はなんかOptionに包まれた値が返る処理
| } match {
| case Success(Some(n)) => Right(n.doSomething)
| case Success(None) => Left("returned None")
| case Failure(e) => Left(e.getMessage)
| }
<console>:14: error: value getLines is not a member of Null
case Success(Some(n)) => Right(n.doSomething)
^
入れ子にしてやれば動くかと思ったけど、そういうことでもないようだ。
scala> Try {
| Option(null)
| } match {
| case Success(n) => n match {
| case Some(g) => Right(g.doSomething)
| case None => Left("returned None")
| }
| case Failure(e) => Left(e.getMessage)
| }
<console>:15: error: value doSomething is not a member of Null
case Some(g) => Right(g.doSomething)
^
なぜこんな挙動になっているのか、どうやって書くのがよいのか、どなたか教えていただけるととてもうれしいです。
ただのSuccess(n)
なら期待した通りに動く。
scala> Try {
| Option(null)
| } match {
| case Success(n) => n
| case Failure(e) => e.getMessage
| }
res31: java.io.Serializable = None
scala> Try {
| Option(123)
| } match {
| case Success(n) => n
| case Failure(e) => e.getMessage
| }
res32: java.io.Serializable = Some(123)