2014年6月30日月曜日

実務者のためのかんたんScalaz

一昨年からEverforthに参画してScalaを使ってとあるシステム開発を行いつつ、クラウドアプリケーションの開発技法について整備してきましたが、ある程度方向性が見えてきたので、札幌の開発部隊に情報展開するための勉強会を企画しました。

クラウドアプリケーションの開発は、ちょうど業務アプリケーション開発にオブジェクト指向技術が導入された時と同じようなパラダイムシフトが起きることになると予想されます。

新しいパラダイムには、難解な理論的な支柱が背景に控えているわけですが、難解な理論を正確に理解することが業務アプリケーション開発を行う必要条件というわけではないのは今も昔も同じです。

難しい部分はフレームワークで吸収したり、イディオム(公式)として覚えてしまう、という方法で多くのケースは対応できるでしょう。

実際のビジネスを展開する上では、新しいパラダイムの上で実案件をこなすことができるエンジニアの層を厚くすることが極めて重要です。

今回は、新しいパラダイムの全体像をつかみやすくすることを目的として、あえてエッジの技術であるScalazをテーマにしてみました。

せっかくなので、Everforth関係者だけでなく、この分野に興味をお持ちの一般のエンジニアの方も参加可能な形にしたいと思います。

平日の昼間なのと告知期間も短く、内容もマニアックなので、あまり外部参加者は多くないと予想されます。

このため、ATNDのようなイベント開催ツールは使わないことにします。興味のある方はお気軽にお越しください。

勉強会の内容

タイトル:

実務者のためのかんたんScalaz

概要:

難しい理論は抜きにしてScalazを用いた、クラウドアプリケーション開発に対応した関数型プログラミングを行うテクニックを解説します。

合わせて、クラウドアプリケーション開発の新しいプログラミング・パラダイムの全体像についての情報共有を行います。

勉強会の構成:

  • 前半(1:30〜3:30): 講義
  • 後半(4:00〜7:00): ハンズオン

場所:

  • 13:30〜17:00 札幌エルプラザの環境研修室1 (開場 13:00)
  • 17:00〜19:00 札幌カフェ5F

場所が連続して確保できなかったので途中で移動が入ります。ちょうどハンズオンの途中になりますが、このタイミングで参加終了しても大丈夫です。逆にハンズオンのみの参加も歓迎です。

環境:

  • コンセント(不明)
  • 無線LAN(不明)

持参頂くもの:

ハンズオンでScalaプログラミングをしていただきますので、以下の環境を整えておいてください。セットアップは、当日他の参加者に手伝ってもらうことができますが、各種モジュールのダウンロードに時間がかかることが予想されるので、必要なモジュールのダウンロードはしておくとよいと思います。

  • PC
  • Scalaコンパイラ
  • sbt
  • Scalaプログラムを編集するエディタ

参加資格:

Everforthの開発に参加しているエンジニア向けですが、一般のエンジニアの参加も歓迎です。

当日会場に直接おいでください。

懇親会:

Scala, Scalaz, 関数型プログラミング、クラウドアプリケーションについての情報交換もかねて懇親会を予定しています。ご都合のよい方はぜひご参加ください。

今後の予定

今回のScalazに続いて以下の勉強会を予定しています。

  • 実務者のためのかんたんScalaプログラミング
  • 実務者のためのかんたんScala入出力プログラミング
  • 実務者のためのかんたんScala設計
  • 実務者のためのかんたんオブジェクト指向分析/設計

シリーズを通して、クラウドアプリケーション開発の土台となる新しいプログラミング・パラダイムの概要をオブジェクト指向分析/設計からのトップダウン、関数型プログラミングからのボトムアップの両面から解説していきます。

全体像をつかむのと同時に、実案件にすぐに入れる基本技術(イディオムなど)の習得も目的としています。

シリーズの全体テーマは、以下のページにあるOFADについての2012年頃の考察を、実システム開発で実践した上でのフィードバックをベースに内容を深化させたものを、実務者向けの切り口で整備したものになります。

2014年6月18日水曜日

Scala Tips/ライブラリ選択(2014年6月バージョン)

どのようなプログラミング言語を使っても、本格的な応用では基本ライブラリだけでニーズが満たせるということはなく、用途に応じて外部ライブラリを併用することになります。

Scalaプログラミングをする上で、ボク的に常に使用するライブラリというのがだいたい固まって来たのでまとめてみました。

scalaz

以下の理由で手放せないライブラリになっています。

  • 「かんたんScalaz」としてご紹介している各種の便利機能
  • 型クラスMonoidの存在
  • 純粋関数型データ構造Tree

また使用頻度は落ちますが、以下の機能も魅力的です。

  • 型クラスTraverse/Foldable
  • 純粋関数型データ構造EphemeralStream
  • Task/Future

最近scalaz-steramを使っていて、どうもTaskが便利らしい、ということが分かってきました。これから使用頻度が高くなるかもしれません。ScalaのFuture/Promiseとの使い分けが悩ましいですね。

scalaz-stream

大規模データに対する操作や、データ操作を並列処理をするための基盤として最近使い始めました。かなりよいです。

scalax.io

Scalaの基本ライブラリはI/O機能が弱いので、本ライブラリは重宝します。以下の2つを使っています。

  • scala-io-core
  • scala-io-file

scala-arm

色々批判もあるようですが、やっぱり便利です。

自作のクラスでもcloseメソッドやdisposeメソッドを定義すれば、自動的にクローズしてくれる機能をヘビーユースしています。

nscala-time

時間周りの処理はやはりJoda-Timeが便利です。そのScalaラッパーということでnscala-timeを使用しています。

dispatch-core

RESTアクセス用に使っています。

難しい記号を多用するのが弱点ですが、色々便利なのは確か。

scalatest

Specs2との二択ですが、ボクはScalaTestの方を使っています。

play-json

Jsonライブラリは、Play上で使っているplay-jsonをPlay外でも使うようになりました。

他にも便利そうなライブラリはありますが、play-jsonが案外高速のようなので無理をして乗り換えるほどではないかな、と今の所は考えています。

anorm & squeryl

DBアクセスはanormとsquerylを併用しています。

anormは、SqlRowが使いやすいのでこれが目当てです。

簡単にちょちょちょっと書きたい所はsquerylが便利。

ただ、この分野はSlickが使いやすければ乗り換えるかもしれません。

2014年6月13日金曜日

実用Scalaz/Option + Monoid

OptionはScalaプログラミングのキーパーツであり、Optionの捌き方の巧拙がScalaプログラミングの効率に直結します。

これと同様にMonoidはScalazプログラミングのキーパーツということができるかと思います。Monoidの捌き方の巧拙がScalazプログラミング、さらにはMonadicプログラミングの効率に直結することになるでしょう。

そして、ScalazのおいてOptionは代表的なMonoidです。つまり、OptionをMonoidとして捌いていく技法はScalazプログラミング、Monadicプログラミングにおいて最重要のテクニックといえるわけです。

というととても難しい技術のように見えますが、(背景の数学的な理論は別として)使い方はとても簡単で、さらに頻出のユースケースをカバーする非常に便利なイディオムです。

準備

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

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]

よくある処理

プログラムを書いているとよく出てくるのが以下のような処理です。

def plus(lhs: Option[Int], rhs: Option[Int]): Option[Int] = {
  lhsの中身とrhsの中身を加算する
  ただしlhsまたはrhsのどちらか一方がNoneの場合はSomeの方の値を使用する
  両方がNoneの場合はNoneにする
}

よくある間違い

この処理を書く場合、Optionを使ってMonadicに処理すると行けそうに思えます。そこで、次のような処理を書いたとしましょう。

def plus(lhs: Option[Int], rhs: Option[Int]): Option[Int] = {
d lhs.flatMap(x => rhs.map(y => x + y))
}

これは、for式を使って以下のように書くこともできます。

def plus(lhs: Option[Int], rhs: Option[Int]): Option[Int] = {
  for (x <- lhs; y <- rhs) yield x + y
}

さて、この結果ですが、以下のようになります。

scala> plus(a, a)
res1: Option[Int] = Some(200)

scala> plus(a, b)
res3: Option[Int] = None

scala> plus(b, a)
res4: Option[Int] = None

scala> plus(b, b)
res2: Option[Int] = None

Monadicに処理した場合は、残念ながらいずれかのOptionがNoneだった場合に、結果もNoneになってしまうわけです。このような処理が適切なケースも多いわけですが、前述の「plus関数」の定義とは異なるのでこの実装は使えません。

Match式

「plus関数」をmatch式を使って実装すると以下のようになります。

def plus(lhs: Option[Int], rhs: Option[Int]): Option[Int] = {
  (lhs, rhs) match {
    case (Some(l), Some(r)) => Some(l + r)
    case (Some(l), None) => Some(l)
    case (None, Some(r)) => Some(r)
    case (None, None) => None
  }
}

このようなOptionの組み合わせごとに処理を分けるmatch式はScalaプログラミングをしているとよく出てくるのではないでしょうか?しかも、かなり野暮ったい感じなのでこれを簡単に書けると好都合です。

Monoid

前出のmatch式による処理をScalazのMonoidで書くと以下になります。複雑なmatch式を演算子「|+|」のみで記述できています。

ScalazではOptionのMonoid演算として、前述のmatch式相当の演算を割り当てているわけですね。

def plus(lhs: Option[Int], rhs: Option[Int]): Option[Int] = {
  lhs |+| rhs
}

この「plus」関数の実行結果は以下になります。

scala> plus(a, a)
res9: Option[Int] = Some(200)

scala> plus(a, b)
res10: Option[Int] = Some(100)

scala> plus(b, a)
res11: Option[Int] = Some(100)

scala> plus(b, b)
res13: Option[Int] = None

おさらい

おさらいの意味でOptionに対してMonoidの演算子「|+|」を適用して演算を行った結果を以下に示します。

scala> 1.some |+| 2.some
1.some |+| 2.some
res40: Option[Int] = Some(3)

scala> 1.some |+| none[Int]
1.some |+| none[Int]
res54: Option[Int] = Some(1)

scala> none[Int] |+| 2.some
none[Int] |+| 2.some
res42: Option[Int] = Some(2)

scala> none[Int] |+| none[Int]
none[Int] |+| none[Int]
res53: Option[Int] = None

参考

Option Index

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

Monoid Index

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

諸元

  • Scala 2.10.4
  • Scalaz 7.0.6

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