OptionのサブクラスであるSomeとNoneを生成するイディオムです。
Scala
Some[Int]とNoneは以下のようにして生成します。(Noneは実際にはシングルトンです。)
val a = Some(10) val a = None
通常はこの生成方法で良いのですが、一つ問題があります。Some(10)の型はOption[Int]ではなくSome[Int]に、Noneの型もOption[Int]ではなくNone[Nothing]になってしまいます。
scala> val a = Some(10) a: Some[Int] = Some(10) scala> val a = None a: None.type = None
このため、型をOption[Int]にするためには以下のように変数定義に記述するか:
val a: Option[Int] = Some(10) val a: Option[Int] = None
オブジェクト側に型注釈を付ける必要があります。
val a = Some(10): Option[Int] val a = None: Option[Int]
いずれもちょっと冗長ですね。
Scalaz
Scalazでは、以下の記述方法でSomeとNoneを生成することが可能です。
val a = 10.some val a = none[Int]
「10.some」のように任意のオブジェクトのsomeメソッド(Scalazが追加)によってOption[Int]型のインスタンスとしてSome[Int]を生成することができます。また、「none[Int]」のように型名を指定することでOption[Int]型のインスタンスとしてNoneを生成することができます。
scala> 1.some res53: Option[Int] = Some(1) scala> none[Int] res54: Option[Int] = None
BooleanをOptionに変換
Scalazでは、Optionを生成する方法としてBooleanのoptinメソッドを使用する方法があります。(Option(6))
def f(cond: Boolean, value: Int): Option[Int] = { cond.option(value) }
条件が真だった場合は指定した値でSomeを、偽だった場合はNoneを作成します。BooleanをOptionに変換する機能ということができます。
ノート
Some(10)
と 10.some
の違いは、一つは見た目として関数型プログラミング的に 10.some
の方が見やすいというのがあると思います。この問題はテーマとしていずれ取り上げたいと思います。これは、あくまで主観的なものなので、見た目の問題だけならどちらの記法を採るのかというのは好みの問題となります。
ということで、もっと実利的な意味で 10.some
が有益なケースというのを知りたいところです。
Scalazの 10.some
という記法が具体的に役に立つのは、関数の引数で型情報まで記述する場合です。
典型的な例がfoldLeftメソッドです。foldLeftメソッドはで畳込みの結果を積算する値の型は、引数に指定する値に付帯する形で指定しなければなりません。
List#foldLeftメソッドを使ってList[Int]の内容を積算するプログラムを例に考えます。Int値がすべて0以上の場合は積算を行いますが、一つでも0未満のものが合った場合はエラーとします。積算が成功した場合はSome[Int]を、失敗した場合はNoneを返します。
まず、初期値として普通にSome(0)を指定してみます。
以下の指定はコンパイルエラーになります。初期値として Some(0)
とするとSome[Int]型になってしまうので、Option[Int]として演算ができなくなってしまうわけです。
def f(l: List[Int]): Option[Int] = { l.foldLeft(Some(0)) { (a, e) => if (e >= 0) a.map(_ + e) else None } }
これは、以下のように型を明記すれば解決しますが、ちょっと冗長ですね。
def f(l: List[Int]): Option[Int] = { l.foldLeft(Some(0): Option[Int]) { (a, e) => if (e >= 0) a.map(_ + e) else None } }
Scalazの 0.some
は型がOption[Int]になるので、この問題が発生しません。
def f(l: List[Int]): Option[Int] = { l.foldLeft(0.some) { (a, e) => if (e >= 0) a.map(_ + e) else none } }
Optionに対するfoldLeftでの畳込み
ついでなのでOption操作の復習も兼ねて、foldLeftでOptionに対する畳込みをいくつか書いてみました。
def f1(l: List[Int]): Option[Int] = { l.foldLeft(0.some) { (a, e) => if (e >= 0) a.map(_ + e) else none } } def f2(l: List[Int]): Option[Int] = { l.foldLeft(0.some) { (a, e) => (e >= 0).fold(a.map(_ + e), none) } } def f3(l: List[Int]): Option[Int] = { l.foldLeft(0.some) { (a, e) => for (x <- a if e >= 0) yield x + e } } def f4(l: List[Int]): Option[Int] = { l.foldLeft(0.some) { (a, e) => a.withFilter(_ => e >= 0).map(_ + e) } }
同じ処理でも色々な書き方ができます。もう少し複雑な処理だと、それぞれの書き方との相性が出てくるので、色々ストックしておいて適材適所で選んでいくようにしたいですね。
関数 | 説明 | 参考 |
---|---|---|
f1 | if式を使った普通の手法 | Option(4) |
f2 | Booleanのfoldを使った手法 | Option(8) |
f3 | for式を使った手法 | Option(7) |
f4 | withFilterとmapを使った手法 | Option(4) |
追記 (2012-02-12)
xuwei_kさんのご指摘で抜けがあることが分かったので補足です。
Some(10)
と 10.some
の比較をして、後者の方がOption[Int]という型になるので、使いやすいという話をしました。この点について補足です。
Scalaの標準ライブラリでは Some(10)
とは別に Option(10)
というOptionの生成方法を用意していて、この場合はOption[Int]型の Some(10)
を生成します。本記事の用途では、Someに関しては Option(10)
の方法でも実現可能です。Scalazを使わない場合は、こちらを使うとよいでしょう。
ただし、Noneに関しては、ちょっとややこしくなります。
Option(null)
で生成できるものの型がOption[NULL]になってしまいます。
格納するオブジェクトがAnyRefである場合には、Option[String](null)
やOption(null: String)
という記法が可能です。(関連「null」)
scala> Option[String](null) res32: Option[String] = None scala> Option(null: String) res29: Option[String] = None
ただし、Anyの場合は使えません。
scala> Option[Int](null) <console>:8: error: type mismatch; found : Null(null) required: Int Note that implicit conversions are not applicable because they are ambiguous: both method Integer2intNullConflict in class LowPriorityImplicits of type (x: Null)Int and method Integer2int in object Predef of type (x: java.lang.Integer)Int are possible conversion functions from Null(null) to Int Option[Int](null) ^ scala> Option(null: Int) <console>:8: error: type mismatch; found : Null(null) required: Int Note that implicit conversions are not applicable because they are ambiguous: both method Integer2intNullConflict in class LowPriorityImplicits of type (x: Null)Int and method Integer2int in object Predef of type (x: java.lang.Integer)Int are possible conversion functions from Null(null) to Int Option(null: Int) ^
以上のようにNoneの場合は、色々と考えないといけないことがあります。この辺の事情を覚えておいてプログラミング中に使い分けても、特にメリットがあるところではないので、Noneを生成する場合は変数や関数シグネチャの型定義またはオブジェクトに対する型注釈で型を補うという方針にしておくのがよいと思います。
scala> None: Option[Int] res31: Option[Int] = None
Scalazを使っている場合は、本文中にあったように、10.some
, none[Int]
の記法を使うようにするのが分かりやすくてよいでしょう。
諸元
- Scala 2.9.1
- Scalaz 6.0.3
Option(10)
返信削除などで、Optionのapplyメソッド使うのは ?
確かにその方法がありました。
返信削除後ほど、情報を追加します。
情報を追加しました。
返信削除ご指摘ありがとうございます。