2012年8月27日月曜日

Scala Tips / Option(15) - First, Last, Max, Min

前回説明した通り、Scalaz 7のOptionはTagを用いてMonoid演算の振舞いを制御できます。

Optionでは以下の4つのTagが定義されています。

  • Tags.First
  • Tags.Last
  • Tags.Max
  • Tags.Min

Tagを指定していない場合を加えると、Monoid演算で5種類の振舞いを選択することができます。

デフォルト

まずデフォルトの動きを確認しましょう。

以下のOptionを定義します。

val a = 1.some
val b = 2.some
val c = 3.some
val n = none[Int]

Monoid演算の結果は以下になります。|+|は2つのMonoid間のMonoid演算、ListのsumrメソッドはMonoidの畳込みです。

scala> a |+| b
res65: Option[Int] = Some(3)

scala> n |+| b
res66: Option[Int] = Some(1)

scala> a |+| n
res67: Option[Int] = Some(2)

scala> n |+| n
res68: Option[Int] = None

scala> List(a, b, c).sumr
res69: Option[Int] = Some(6)

scala> List(n, b, c).sumr
res70: Option[Int] = Some(5)

scala> List(a, n, c).sumr
res71: Option[Int] = Some(4)

scala> List(a, b, n).sumr
res72: Option[Int] = Some(3)

Tags.First

次はTags.Firstのタグ付けをした場合です。

val a: Option[Int] @@ Tags.First = Tag(1.some)
val b: Option[Int] @@ Tags.First = Tag(2.some)
val c: Option[Int] @@ Tags.First = Tag(3.some)
val n: Option[Int] @@ Tags.First = Tag(none)

Monoid演算の結果は以下になります。Monoid演算の結果、最初に有効なOptionが選択されていることが分かります。

scala> a |+| b
res37: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(1)

scala> a |+| n
res40: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(1)

scala> n |+| b
res39: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(2)

scala> n |+| n
res41: scalaz.@@[Option[Int],scalaz.Tags.First] = None

scala> List(a, b, c).sumr
res48: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(1)

scala> List(n, b, c).sumr
res38: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(2)

scala> List(a, n, c).sumr
res55: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(1)

scala> List(a, b, n).sumr
res56: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(1)
Tagで型を指定

First.Tagの指定をTagメソッド(scalaz.Tagのapplyメソッド)で行うこともできます。

val a = Tag[Option[Int], Tags.First](1.some)
val b = Tag[Option[Int], Tags.First](2.some)
val c = Tag[Option[Int], Tags.First](3.some)
val n = Tag[Option[Int], Tags.First](none)
FirstOption

OptionのTags.First用に専用の型としてFirstOptionが定義されています。Scalaz 6のFirstOptionは専用のトレイトでしたが、似たような使い方ができます。

val a: FirstOption[Int] = Tag(1.some)
val b: FirstOption[Int] = Tag(2.some)
val c: FirstOption[Int] = Tag(3.some)
val n: FirstOption[Int] = Tag(none)
firstメソッド

Scalaz 7のOptionはFirstOptionを取得するfirstメソッドを提供しています。Scalaz 6のfstメソッドに対応します。

val a = 1.some.first
val b = 2.some.first
val c = 3.some.first
val n = none[Int].first

Tags.Last

次はTags.Lastのタグ付けをした場合です。

val a: Option[Int] @@ Tags.Last = Tag(1.some)
val b: Option[Int] @@ Tags.Last = Tag(2.some)
val c: Option[Int] @@ Tags.Last = Tag(3.some)
val n: Option[Int] @@ Tags.Last = Tag(none)

Monoid演算の結果は以下になります。Monoid演算の結果、最後に有効なOptionが選択されていることが分かります。

scala> a |+| b
res73: scalaz.@@[Option[Int],scalaz.Tags.Last] = Some(2)

scala> a |+| n
res74: scalaz.@@[Option[Int],scalaz.Tags.Last] = Some(1)

scala> n |+| b
res75: scalaz.@@[Option[Int],scalaz.Tags.Last] = Some(2)

scala> n |+| n
res76: scalaz.@@[Option[Int],scalaz.Tags.Last] = None

scala> List(a, b, c).sumr
res77: scalaz.@@[Option[Int],scalaz.Tags.Last] = Some(3)

scala> List(n, b, c).sumr
res78: scalaz.@@[Option[Int],scalaz.Tags.Last] = Some(3)

scala> List(a, n, c).sumr
res79: scalaz.@@[Option[Int],scalaz.Tags.Last] = Some(3)

scala> List(a, b, n).sumr
res80: scalaz.@@[Option[Int],scalaz.Tags.Last] = Some(2)
Tagで型を指定

Last.Tagの指定をTagメソッド(scalaz.Tagのapplyメソッド)で行うこともできます。

val a = Tag[Option[Int], Tags.Last](1.some)
val b = Tag[Option[Int], Tags.Last](2.some)
val c = Tag[Option[Int], Tags.Last](3.some)
val n = Tag[Option[Int], Tags.Last](none)
LastOption

OptionのTags.Last用に専用の型としてLastOptionが定義されています。Scalaz 6のLastOptionは専用のトレイトでしたが、似たような使い方ができます。

val a: LastOption[Int] = Tag(1.some)
val b: LastOption[Int] = Tag(2.some)
val c: LastOption[Int] = Tag(3.some)
val n: LastOption[Int] = Tag(none)
lastメソッド

Scalaz 7のOptionはLastOptionを取得するlastメソッドを提供しています。Scalaz 6のsndメソッドに対応します。

val a = 1.some.last
val b = 2.some.last
val c = 3.some.last
val n = none[Int].last

Tags.Max

次はTags.Firstのタグ付けをした場合です。

val a: Option[Int] @@ Tags.Max = Tag(1.some)
val b: Option[Int] @@ Tags.Max = Tag(2.some)
val c: Option[Int] @@ Tags.Max = Tag(3.some)
val n: Option[Int] @@ Tags.Max = Tag(none)

Monoid演算の結果は以下になります。Monoid演算の結果、最大値の値を持つOptionが選択されていることが分かります。

scala> a |+| b
res81: scalaz.@@[Option[Int],scalaz.Tags.Max] = Some(2)

scala> n |+| b
res82: scalaz.@@[Option[Int],scalaz.Tags.Max] = Some(1)

scala> a |+| n
res83: scalaz.@@[Option[Int],scalaz.Tags.Max] = Some(2)

scala> n |+| n
res84: scalaz.@@[Option[Int],scalaz.Tags.Max] = None

scala> List(a, b, c).sumr
res85: scalaz.@@[Option[Int],scalaz.Tags.Max] = Some(3)

scala> List(n, b, c).sumr
res86: scalaz.@@[Option[Int],scalaz.Tags.Max] = Some(3)

scala> List(a, n, c).sumr
res87: scalaz.@@[Option[Int],scalaz.Tags.Max] = Some(3)

scala> List(a, b, n).sumr
res88: scalaz.@@[Option[Int],scalaz.Tags.Max] = Some(2)
Tagで型を指定

Fisrt.Tagの指定をTagメソッド(scalaz.Tagのapplyメソッド)で行うこともできます。

val a = Tag[Option[Int], Tags.Max](1.some)
val b = Tag[Option[Int], Tags.Max](2.some)
val c = Tag[Option[Int], Tags.Max](3.some)
val n = Tag[Option[Int], Tags.Max](none)
MaxOption

OptionのTags.Max用に専用の型としてMaxOptionが定義されています。

val a: MaxOption[Int] = Tag(1.some)
val b: MaxOption[Int] = Tag(2.some)
val c: MaxOption[Int] = Tag(3.some)
val n: MaxOption[Int] = Tag(none)

OptionのTags.Max向けに、firstメソッドに対応するメソッドはないようです。

Tags.Min

次はTags.Minのタグ付けをした場合です。

val a: Option[Int] @@ Tags.Min = Tag(1.some)
val b: Option[Int] @@ Tags.Min = Tag(2.some)
val c: Option[Int] @@ Tags.Min = Tag(3.some)
val n: Option[Int] @@ Tags.Min = Tag(none)

Monoid演算の結果は以下になります。Monoid演算の結果、最も小さな値としてSome(1)またはNoneが選択されていることが分かります。OptionのTags.Minは、最小値として値が設定されないNoneを選択する仕様になっているようです。ボクの直感ではNoneでない場合は、Some(1)を最小値とすると考えていたので、個人的に注意が必要な仕様と認識しました。

scala> a |+| b
res89: scalaz.@@[Option[Int],scalaz.Tags.Min] = Some(1)

scala> n |+| b
res90: scalaz.@@[Option[Int],scalaz.Tags.Min] = None

scala> a |+| n
res91: scalaz.@@[Option[Int],scalaz.Tags.Min] = None

scala> n |+| n
res92: scalaz.@@[Option[Int],scalaz.Tags.Min] = None

scala> List(a, b, c).sumr
res93: scalaz.@@[Option[Int],scalaz.Tags.Min] = None

scala> List(n, b, c).sumr
res94: scalaz.@@[Option[Int],scalaz.Tags.Min] = None

scala> List(a, n, c).sumr
res95: scalaz.@@[Option[Int],scalaz.Tags.Min] = None

scala> List(a, b, n).sumr
res96: scalaz.@@[Option[Int],scalaz.Tags.Min] = None
Tagで型を指定

Min.Tagの指定をTagメソッド(scalaz.Tagのapplyメソッド)で行うこともできます。

val a = Tag[Option[Int], Tags.Min](1.some)
val b = Tag[Option[Int], Tags.Min](2.some)
val c = Tag[Option[Int], Tags.Min](3.some)
val n = Tag[Option[Int], Tags.Min](none)
MinOption

OptionのTags.Min用に専用の型としてMinOptionが定義されています。

val a: MinOption[Int] = Tag(1.some)
val b: MinOption[Int] = Tag(2.some)
val c: MinOption[Int] = Tag(3.some)
val n: MinOption[Int] = Tag(none)

OptionのTags.Min向けに、firstメソッドに対応するメソッドはないようです。

ノート

関数型プログラミングのポイントの一つは、アルゴリズムを様々なデータ構造に対していかに再利用していくのかという点にあると思います。別の言い方をするとアルゴリズムとデータ構造を疎結合するためのメカニズムが論点になります。

Monoidという抽象および型クラスによる実行メカニズムは、Monoidを操作するアルゴリズムを、操作対象のデータ構造から疎結合にするために大きく寄与します。特にfold系の畳込みとMonoidの組合せは、関数型プログラミングにおける最重要イディオムの一つということができるでしょう。

ここまでがScalaz 6の成果ですが、Scalaz 7ではTagというメカニズムが加わり、さらにアルゴリズムを再利用できる範囲が広がりました。OptionのようなコンテナをMonoidとして使用する場合、コンテナに格納されているオブジェクトに対するMonoid演算は複数の可能性が考えられます。Scalaz 6のMonoidではその中の一つの選択(オブジェクト同士をMonoid演算する)に決め打ちにしていたわけですが、Scalaz 7ではTagの導入により、Tagの指定で選択を細かく指定することができるようになりました。

アルゴリズム側は全く意識することなく、Monoidのパラメータを指定するときのTag付けのみで、Monoid演算の細かな振舞いを切り替えることができます。このメカニズムにより、Monoidを操作対象とするアルゴリズムの適用範囲が更に広がることになります。

追記(2012-08-28)

Kenji YoshidaさんからMaxOptionとMinOptionが存在している旨の、ご指摘あったので、内容を修正しました。どうもありがとうございます。

また、ねこはるさんからご指摘のあったTags.First, Tags.Last, Tags.Max, Tags.Minメソッドは「Option(16) - First, Last, Max, Min その2」で情報を追加しました。

諸元

  • Scala 2.10.0-M6
  • Scalaz 7.0.0-M2

2 件のコメント:

  1. > OptionのTags.Maxでは、FirstOptionに対応する型やfirstメソッドに対応するメソッドはないようです。

    First,Last,Max,Min に関して、package object にすべて alias が定義されてます

    https://github.com/scalaz/scalaz/blob/v7.0.0-M2/core/src/main/scala/scalaz/package.scala#L156-159

    返信削除
  2. 情報有り難うございます。
    確かにMaxOption,MinOptionのaliasはありますね。訂正しておきます。

    返信削除