2014年5月26日月曜日

かんたんScalaz/Option編

かんたんScalazのOption編です。

Scalaプログラミングでは、Optionはキーとなる重要なオブジェクトで、Optionの取り回し方の優劣がプログラミングの精度・効率に大きく作用します。

ScalazでもOption向けに多くの機能を用意しています。

「かんたんScalaz」ということでScalazが提供しているMonadやMonoidといった難しい概念を抜きにして便利に使えるOptionの機能をまとめてみました。

準備

説明の準備に以下の関数を定義します。

scala> def a: Option[Int] = Some(100)
def a: Option[Int] = Some(100)
a: Option[Int]

scala> def b: Option[Int] = None
def b: Option[Int] = None
b: Option[Int]

?と|

OptionをBoolean型に見立てて値を決めたい場合に、3項演算子的な書き方ができると便利です。Scalazでは「?」と「|」の組合せでこれが実現できます。

scala> a ? "defined" | "undefined"
a ? "defined" | "undefined"
res12: String = defined

scala> b ? "defined" | "undefined"
b ? "defined" | "undefined"
res13: String = undefined

Scala基本機能のみでもif式で以下のように書ける上に実行性能も圧倒的にこちらの方が速いので、通常はこれでも十分ですが、上記の方が短く書けるので好みによって使ってみるのもよいと思います。

scala> if (a.isDefined) "defined" else "undefined"
if (a.isDefined) "defined" else "undefined"
res44: String = defined

個人的には「?」と「|」の方が可読性が高いように思うので、性能が気にならないところ(例:フレームワークのコア機能以外)ではよく使っています。

OptionがSomeだった場合は格納された値、そうでなかった場合はデフォルト値を取得する処理はよく出てきます。

通常は、OptionのgetOrElseメソッドを使いますが、Scalazでは「|」でこれを記述することができます。

scala> a | 10
a | 10
res17: Int = 100

scala> b | 10
b | 10
res18: Int = 10

個人的には「|」の方が可読性が高いように思うので、性能が気にならないところ(例:フレームワークのコア機能以外)ではよく使っています。

some/none

OptionがSomeだった場合は格納された値に演算を施した値、そうでなかった場合はデフォルト値を取得する処理もよく出てきます。

Scalaの基本機能で記述する場合、以下のようになると思います。

scala> a match {
  case Some(x) => x + 100
  case None => 0
}
res45: Int = 200

scala> a.map(_ + 100).getOrElse(0)
a.map(_ + 100).getOrElse(0)
res46: Int = 200

どちらの方法でもよいですが、頻出処理なのでもっと簡略化した記法が使えるとうれしいところです。

Scalazではこの目的で「some」と「none」の組合せでの記述方法を用意しています。

scala> a some(_ + 100) none(0)
a some(_ + 100) none(0)
res15: Int = 200

scala> b some(_ + 100) none(0)
b some(_ + 100) none(0)
res16: Int = 0

また前述の「|」を使用した以下の記述方法も便利です。

scala> a.map(_ + 100) | 0
a.map(_ + 100) | 0
res47: Int = 200

orZero

orZeroメソッドは「かんたんScalaz/Boolean編」で紹介した「??」メソッドや「!?」メソッドで用いているMonoidの性質を利用したメソッドです。

OptionがSomeの場合は格納された値を返しますが、そうでない場合は格納された値のMonoidとしての単位元を返します。このためMonoidである型にしか適用できませんが、基本データ型やコレクションなどはほとんどMonoidなのでかなり適用範囲は広いです。

scala> a.orZero
a.orZero
res22: Int = 100

scala> b.orZero
b.orZero
res23: Int = 0

上記プログラムの動きは以下のようになります。

OptionがSomeの場合は、Someに格納されている値である100が返ります。一方Noneの場合は、以下の動きになっています。

  • Optionに格納されている型はInt
  • Intの単位元は「0」
  • OptionがNoneなのでIntの単位元である「0」を返す

Monoidというと敷居が高いので、「かんたんScalaz」の文脈では型ごとに初期値を持っている、ぐらいの捉え方でよいと思います。この「初期値」は、数値系なら「0」、ListなどのコレクションはNilなどの「空」となりますので、0や空といった値になると覚えておいて、実際の値は必要に応じて調べるというアプローチでよいでしょう。

「〜」を使う以下の書き方も用意されていますが、見落としや誤読してしまいそうなのでボクは使わないようにしています。

scala> ~a
res48: Int = 100

scala> ~b
res49: Int = 0

参考

Option Index

Optionに関する2012年ごろのブログのまとめです。

Optionに関しては、この当時とそれほど見方は変わっていません。このため、このあたりの記事も現役として参考にしていただけると思います。

ただ、プログラミングしている中で色々なバランスが見えてきた部分もあると思うので、棚卸しをした上で適宜追加情報や新しいバランス上での使い方についてまとめていきたいと思います。

諸元

  • Scala 2.10.4
  • Scalaz 7.0.6

2014年5月19日月曜日

かんたんScalaz/Booean論理記号

ScalazではBooleanに以下の論理演算子を追加しています。







演算子意味別名Scala
Conjunction, AND/\&&
Disjunction, OR\/||
!||Negation of Disjunction, NOR
!&&Negation of Conjunction, NAND
ーー>Conditional
<ーーInverse Conditional
Negational of Conditionalー/>
Negation of Inverse Conditional<\ー

かんたんScalazの観点では、以下のような扱いがよいと思います。

  • 「ならば」の論理演算である「ーー>」は覚える価値がある。
  • 他の演算子は必要に応じて。

ーー>

Scala(や通常のプログラミング言語)が提供していない論理演算子の中で「ならば(Conditional, 論理包含、条件文)」を表す論理演算子「ーー>」はなかなか有用ではないかと思います。「ーー>」の使い所はたとえば以下のようなassertです。

assert (!isCacheable(x) --> !isCached(x), "キャッシュ可能でない場合、キャッシュにデータが入っているのはおかしい。")

このケースだと、キャッシュ可能の場合はキャッシュにデータがあってもなくてもよいので、「ならば」の論理演算の条件にぴったり合います。

ちなみに、上記のassertは「<ーー」(Inverse Conditional, 逆論理包含?)を使って以下のようにも書けるようです。

assert (isCacheable(x) <-- isCached(x), "キャッシュにデータが入っている場合、キャッシュ可能である必要がある。")

∧と∨

論理積の演算子に「∧」や「/\」、論理和の演算子に「∨」や「\/」が定義されていますが、Scalaの論理演算子「&&」、「||」とかぶるので使用しても通常のプログラミングが特に便利になるというものではありません。逆に実行時のオーバーヘッドは確実にあるので、「∧」や「∨」といった数学記号をどうしても使いたい局面でなければ無理をして使う感じでもないでしょう。

否定形

以下のものは他の演算子の否定形です。否定形があると覚えておいて、使う時に仕様を確認する形でよいでしょう。





論理記号別名否定元
!||||
!&&&&
ー/>ーー>
<\ー<ーー

諸元

  • Scala 2.10.4
  • Scalaz 7.0.6

2014年5月12日月曜日

かんたんScalaz/Boolean編

Scalaz 7を本格的に使い始めたので、Scalaz 7を包含したコーディング・イディオムの棚卸しをしています。Scala 2.10の基本機能とScalaz 7を併用する前提で、コーディングの局面毎に使用するイディオムを事前に準備しておくわけです。イディオムを事前準備しておくことで、プログラミングの局面局面で即断即決ができるので効率よくプログラミングを進めることができます。またイディオムは利便性や実行速度などの要因を考慮に入れて使い所を絞り込んでおくので、プログラムの品質や性能などの向上も期待できます。

Scalazは、MonadやMonoidといった代数系の概念を軸にした型クラスやや純粋関数型データ構造を提供するライブラリですが、小回りの効いた便利機能も数多く提供しています。これらの便利機能は特にMonadやMonoidといった概念を抜きにして、便利につかえるので「かんたんScalaz」としてまとめていきたいと思います。

今回はBoolean向けの便利機能です。

準備

説明の準備に以下の関数を定義します。

scala> def t: Boolean = true
def t: Boolean = true
t: Boolean

scala> def f: Boolean = false
def f: Boolean = false
f: Boolean

option

ScalazではBooleanにoptionメソッドを追加しています。optionメソッドは、Booleanがtrueであれば指定された値を返すSomeを、falseであればNoneを返します。

使い方は以下になります。

scala> t option 100
t option 100
res3: Option[Int] = Some(100)

scala> f option 100
f option 100
res4: Option[Int] = None

これをScalaネイティブの文法で書くとif式を用いた以下になります。これでも問題ないといえばないのと実効速度は確実に速いので、Scalazのoptionメソッドは無理をして使う必要はありませんが、プログラミング時にはかゆいところに手が届くという感じで便利なんですよね。

if (t) Option(100) else None

たとえば、次のような感じでBooleanがtrueの時の処理を長めに書く時に重宝します。

t option {
  val a = somework
  val b = somework(a)
  somework(b)
}

??と!?

ScalazではMonoidという型クラスを提供していますが、これがすこぶる便利なんです。

その便利さの一つが単位元です。Monoidの性質を持つオブジェクトは、単位元という特別なインスタンスが定義されます。この単位元をデフォルト値や初期値として用いることで、プログラミングを簡略化するテクニックがありますが、この実例の一つがBooleanの「??」メソッドと「!?」メソッドです。

「??」メソッドは、Booleanがtrueであれば指定された値を、falseの場合は型の単位元を返します。

具体的には以下のような動きになります。

scala> t ?? 100
t ?? 100
res5: Int = 100

scala> f ?? 100
f ?? 100
res6: Int = 0

Booleanの値がtrueの場合は、指定された値である100が返ります。一方falseの場合は、以下の動きになっています。

  • 指定された値「100」の型はInt
  • この式全体の型はInt
  • Intの単位元は「0」
  • Booleanの値がfalseなのでIntの単位元である「0」を返す

このようにMonoidの性質とScalaの型推論のコンビネーションで、非常にコンパクトに目的の式を記述することができるわけです。

Monoidというと敷居が高いので、「かんたんScalaz」の文脈では型ごとに初期値を持っている、ぐらいの捉え方でよいと思います。この「初期値」は、数値系なら「0」、ListなどはNilとなりますので、0や空といった値になると覚えておいて、実際の値は必要に応じて調べるというアプローチでよいでしょう。

?!

「??」メソッドの否定形は「?!」メソッドになります。

scala> t !? 100
t !? 100
res165: Int = 0

scala> f !? 100
f !? 100
res167: Int = 100

? |

Javaで提供されていた3項演算子は、Scalaには取り込まれずif式を使用するという建付けになっています。

scala> if (t) 100 else 200
if (t) 100 else 200
res0: Int = 100

Scalazでは「?」メソッドと「|」メソッドのコンビネーションで3項演算子っぽく記述することが可能です。

scala> t ? 100 | 200
t ? 100 | 200
res1: Int = 100

scala> f ? 100 | 200
f ? 100 | 200
res2: Int = 200

「?」メソッド&「|」メソッドとは別にfoldメソッドというのも用意されていますが、こちらは特段便利になったような感じはしません。if式と比べると実行速度は確実に落ちるので、foldを使うのならif式を使う方がよさそうです。

scala> t fold (100, 200)
t fold (100, 200)
res9: Int = 100

scala> f fold (100, 200)
f fold (100, 200)
res10: Int = 200

「?」メソッド&「|」メソッドもif式に比べると実行速度は確実に落ちるので無理をして使うほどのことはありませんが、短くかけるとプログラミングが楽になる局面(式を1行に収めたい等)はあるので、コーディング・イディオムに加えておくのもよいと思います。

参考

Boolean

2012年のブログです。

Booleanについてはあまり認識は変わっていないようです。

ただ、2012年当時とは色々とアプローチが変わった部分もあるので、技術の棚卸しという意味で過去のブログで取り上げた内容も再度取り上げていく予定です。

Monoid

Monoidに関する2012年ごろのブログのまとめです。

Scalazが提供する代数概念の型クラスのうち、MonadはScalaネイティブでも基本機能は持っていますし、Aplicativeはfor式で代替することが可能です。

しかし、Monoidだけは該当する機能がありません。このMonoidが非常に便利なので、これを使うためにScalazを使うという面が大きいです。

諸元

  • Scala 2.10.4
  • Scalaz 7.0.6

2014年5月7日水曜日

Scala 2.10

つい先日Scala 2.11がリリースされたばかりですが、タイトルの「Scala 2.10」は間違いではありません。

一昨年の後半からとあるシステム開発に注力していたため時間が取れずブログ更新を中断していましたが、ようやく一段落してきたので、自分向けに技術情報の棚卸しをしていくという意味でもぼちぼち書いていきたいと思います。

最近うれしかったことの一つはようやく製品開発にScala 2.10+Scalaz 7を使えるようになったことです。システムは一度動き始めると、なかなか使用しているコンパイラ、ライブラリ、ミドルウェアのバージョンを上げることはできません。特にバージョン間での互換性が高くないことで有名(?)なScalaの場合はなおさらですが、Akkaを本格的に使おうとするとさすがにそうもいっておられず、大量のコンパイルエラーと警告を克服してやっとScala 2.10+Scalaz 7に上げることができたしだいです。

ということで、(いまさらですが)Scala 2.10を本格的に使ってみた感想です。

  • warningの強化
  • Try
  • Future
  • Implicit Class/Value Class
  • feature機能
  • String Interpolation

warningの強化

Scala 2.10で、予想外にうれしかったことが「==」の型チェック機能の強化です。この機能のお陰で移植時に復数のバグを見つけることができました。

scala> val x = Some("5")
val x = Some("5")
x: Some[String] = Some(5)

scala> val y = "5"
val y = "5"
y: String = 5

scala> println(x == y)
println(x == y)
<console>:16: warning: comparing values of types Some[String] and String using `==' will always yield false
              println(x == y)
                        ^
false

ただ、改めて試してみると:

def f(a: String, oa: Option[String]): Boolean = a == oa

といったケースでは警告はでないようです。

また:

List(1, 2, 3).contains("5")

といったcontainsメソッドの型チェック問題は依然としてオープンです。

==メソッドやcontainsメソッドにおける型安全な等価性は、Scalaプログラミングで非常に重要なプログラミングテクニックだと思うので、いずれ取り上げてみたいと思います。

Try

Tryの導入によって2.10でプログラミングスタイルが大きく変わることは想定済みだったので、TryとNonFatalは2.9にバックポートして使っていました。このため、特に大きな問題はなく移行することができました。

想定外だったのが、ScalazでTryをMonadとして扱うことができない点です。例外が発生した時のTryの振舞いがモナド則を満たさないという問題があるようで、それが原因でScalaz側でTryの各種型クラスは提供していないのかなと思います。

ScalazでTryをサポートしていないため、Tryを使ってMonadicプログラミングすることに限界が出てきます。この辺りの事情も織り込んだ上で、例外をハンドリングする作戦を考えていく予定です。

Future

FutureはAkka Futureをほぼimport先を切り替えるだけで移植できました。

想定外だったのが、Tryと同様にScalazでMonadとして扱えない点です。Tryと同様に例外は発生した時の振舞いがモナド則を満たさないのが原因ではないかと推測します。

Scalazは、独自にFutureを用意しているので、MonadicプログラミングをするためにはこちらのFutureを使う事になりそうですが、本家Futureと併用することになるのでできれば避けたいところです。

Implicit Class/Value Class

Implicit Class/Value Classは、自分で直接使うことは今の所ほぼありません。ただ、型クラス機能を使用する際のオーバーヘッドが大幅に減少したはずなので、Scalazのような型クラスを多用するライブラリを安心して使えるようになりました。心理的な効果が非常に大きいです。

feature機能

implicit conversionはScalaの魅力的な機能の一つですが、バグが出た時のデバッグがとても大変になるので、ボクのコーディング戦略ではほぼ使わない機能になっています。具体的には、いわゆる「Pimp My Libraryパターン」で既存クラスを文脈依存で拡張したり、DSLの枠組み上必要な場合、以外は使わない機能です。

Scala 2.10からfeature機能が入って、拡張機能はコンパイルのパラメタかプログラム内で指定しないと警告が出るようになりました。上述のimplicit conversionもこの拡張機能に入っているので警告対象になりました。無自覚に使っている場合も警告によって存在を認識することができ重宝しました。

このimplicit conversion以外にも、今回のScala 2.10化では以下の警告が出ました。

  • postfixOps
  • existentials

postfixOpsはDSL的には残念ですが、非推奨の文法になったということなので、ソースコードを修正して対応しました。

プログラミング言語は互換性も大事ですが、Scalaのような伸び盛りの言語は新しい応用を取り込んでいくチャレンジも重要なので、feature機能とdeprecation機能によって時代遅れになってしまった機能、一般の開発者は使わない方がよい機能をうまく制御していくアプローチは非常によいと思いました。

String Interpolation

String Interpolationは:

  • s Interplatorやf Interpolatorの機能
  • String Interpolationの外部追加Iterpolatorへの期待
  • String InterpolationのInterpolator機能の開発

といった所が切り口になりますが、今の所はs Interplatorがそれなりに便利かな、という利用レベルです。

以前はformatメソッドを使っていましたが、引数の数を間違えると以下のようなランタイムエラーが出るのが問題点としてありました。この問題が起きなくなるのが、体感的に感じたs Interplator導入の直接のメリットでした。もちろん、s Interplatorの方が若干性能向上も期待できるのは重要です。

> "%s = %s".format("1 + 1")
java.util.MissingFormatArgumentException: Format specifier 's'
...

SlickのSQL Interpolationといった応用が沢山出てくると色々と面白くなってきそうです。

Scala 2.11

Scala 2.11については、とりあえずはPlayの対応待ちというところです。

Scala 2.11はTuple /Case classの22個制限が外れるのが大きな魅力ですが、それ以外は特別に使ってみたい機能はないようなので、実際に使いはじめるのはまた1年後ぐらいということになるかもしれません。