2014年6月2日月曜日

かんたんScalaz/Option編 SomeとNone

Optionを使う場合にSomeやNoneを作成する処理は当然ながら頻出です。

scala> val a = Some(5)
val a = Some(5)
a: Some[Int] = Some(5)

scala> val b = None
val b = None
b: None.type = None

通常はこれで問題ないのですが、若干扱いにくい点があります。

上記処理で得られる型にご注目下さい。

「Some(5)」を代入(束縛?)した変数aの型がSome[Int]になっています。さらに、Noneを代入した変数の方がNone.typeになっています。

「Some(5)」や「None」は、型「Option[Int]」の値として扱われる事が多いので、「Some(5)」や「None」を型「Option[Int]」として生成する手段があるとさらに便利になります。

たとえば以下のようなメソッド定義のケースです。この場合、メソッドの返却値の型の定義を省略することができます。

scala> def getFoo = Some(5)
def getFoo = Some(5)
getFoo: Some[Int]

scala> def getBar = None
def getBar = None
getBar: None.type

またよくあるのは以下のような畳み込み処理の初期値です。畳み込みの初期値の型が「Option[Int]」ではなく「Some[Int]」になっているため、式全体の演算結果としてNoneを返すことができなくなっています。

scala> Vector(1, 2, 3, 4, 5).foldLeft(Some(0))((z, x) => if (x % 2 == 0) None else z.map(_ + x))
se z.map(_ + x))
<console>:14: error: type mismatch;
 found   : None.type
 required: Some[Int]
              Vector(1, 2, 3, 4, 5).foldLeft(Some(0))((z, x) => if (x % 2 == 0) None else z.map(_ + x))
                                                                                ^

Scalaの解

まず、この問題に対するScalaでの対策です。

値がある場合はOption#applyメソッドを使用、値がない場合はOption#emptyメソッドに型「Int」を指定することで型「Option[Int]」を得ることができます。

scala> Option(5)
Option(5)
res14: Option[Int] = Some(5)

scala> Option.empty[Int]
Option.empty[Int]
res16: Option[Int] = None

前述の例をこの方法で書きなおしたものが以下になります。

scala> def getFoo = Option(5)
def getFoo = Option(5)
getFoo: Option[Int]

scala> def getBar = Option.empty[Int]
def getBar = Option.empty[Int]
getBar: Option[Int]

scala> Vector(1, 2, 3, 4, 5).foldLeft(Option(0))((z, x) => if (x % 2 == 0) None else z.map(_ + x))
else z.map(_ + x))
res17: Option[Int] = None

Scalazの解

Scalazではこの問題に対応するため、任意の値を型「Option[T]」のSome[T]またはNoneに持ち上げる機能を提供しています。

scala> 5.some
res30: Option[Int] = Some(5)

scala> none[Int]
res33: Option[Int] = None

前述の例をこの方法で書き直したものが以下になります。

scala> def getFoo = 5.some
getFoo: Option[Int]

scala> def getBar = none[Int]
getBar: Option[Int]

scala> Vector(1, 2, 3, 4, 5).foldLeft(0.some)((z, x) => if (x % 2 == 0) None else z.map(_ + x))
e z.map(_ + x))
res18: Option[Int] = None

個人的な好みの問題もありますがScala版に対して以下の優位点があると思います。

  • プログラミング時に書きやすい
  • 可読性が高い

具体的には「5.some」の記法は「Option(5)」の記法と比べて以下の優位点があると思います。

プログラミング時の書きやすさ

Scala版では、プログラミング時に「5」と書いた後にSome化したいと思った場合、一度カーソルを前に戻して「Some(」を書いた後、カーソルを文末に移動させて「)」を閉じるという作業が必要になります。それに対して、Scalaz版では「5」と書いた後にSome化したいと思った場合「.some」と書くだけですみます。プログラミングの流れを止めません。

またScala版では「Some(5)」が欲しい時に、場合によっては「Option(5)」にしないといけないので、毎回判断が必要になります。Scalaz版ではどの場合でも「5.some」でOKです。

可読性

Scala版では「Some(5)」の意味で「Option(5)」を使うことがありますが、Optionというオブジェクト名を使用するため、微差ですが意図がわかりづらい面があると思います。

別の切り口として、重要な情報が左側に来る方が可読性が高くなると思います。これを前提にすると、Scala版の「Some(5)」の場合「Some」であることが重要で、「5」の重要度はその次という印象になります。一方、Scalaz版の「5.some」の場合「5」の値が重要で、「some」の重要度はその次という印象になります。多くの場合、「5」の方が重要なので、(これまた微差ですが)「Some(5)」より「5.some」の方が可読性が優れているのではないかと思います。

Scalaz版のまとめ

「プログラミング時の書きやすさ」、「可読性」のいずれも微差なので、好みの方法を使えばよいわけですが、ボクはScalazの提供する「5.some」、「none[Int]」の記法を愛用しています。

参考

今回の記事は、基本的には2012年の以下の記事と同じ内容を再整理したものです。

棚卸しという意味で記事化しました。

諸元

  • Scala 2.10.4
  • Scalaz 7.0.6

0 件のコメント:

コメントを投稿