2015年1月14日水曜日

Operationalモナドの合成

ScalazでOperationalモナドが簡単に使えることが分かったので、次の段階としてxuweiさんの書かれた「CoproductとInjectを使ったFree Monadの合成とExtensible Effects」を参考に、Operationalモナド(Freeモナド)の合成を試してみました。

CoproductとInjectについて、理論や動作メカニズムを把握できていないのでほぼ写経の状態です。

ConsoleService

まず最初は前回作成したConsoleServiceを合成可能にチューニングします。

プログラムの見通しをよくするため今回のテーマに関係しないinterpreterTaskとrunTaskは省いています。

package sample

import scala.language.higherKinds
import scalaz._, Scalaz._

object ConsoleService {
  sealed trait ConsoleOperation[_]
  case class PrintLine(msg: String) extends ConsoleOperation[Unit]
  case object ReadLine extends ConsoleOperation[String]

  def printLine(msg: String) = Free.liftFC(PrintLine(msg))
  def readLine = Free.liftFC(ReadLine)

  val interpreter = new (ConsoleOperation ~> Id) {
    def apply[T](c: ConsoleOperation[T]): Id[T] = {
      c match {
        case PrintLine(msg) => println(msg)
        case ReadLine => scala.io.StdIn.readLine()
      }
    }
  }

  def run[T](f: Free.FreeC[ConsoleOperation, T]): T = {
    Free.runFC(f)(interpreter)
  }

  class ConsolePart[F[_]](implicit I: Inject[ConsoleOperation, F]) {
    def printLine(msg: String): Free.FreeC[F, Unit] = Free.liftFC(I.inj(PrintLine(msg)))
    def readLine: Free.FreeC[F, String] = Free.liftFC(I.inj(ReadLine))
  }

  object ConsolePart {
    implicit def instance[F[_]](implicit I: Inject[ConsoleOperation, F]): ConsolePart[F] = new ConsolePart[F]
  }
}

追加したのは、scalaz.InjectによってOperationalモナドの合成を行うための受け皿となるクラスConsolePartとそのコンパニオンオブジェクトです。

class ConsolePart[F[_]](implicit I: Inject[ConsoleOperation, F]) {
    def printLine(msg: String): Free.FreeC[F, Unit] = Free.liftFC(I.inj(PrintLine(msg)))
    def readLine: Free.FreeC[F, String] = Free.liftFC(I.inj(ReadLine))
  }

  object ConsolePart {
    implicit def instance[F[_]](implicit I: Inject[ConsoleOperation, F]): ConsolePart[F] = new ConsolePart[F]
  }

ConsoleServiceオブジェクト本体に定義されているprintLine関数、readLine関数の合成可能版を定義しています。

暗黙パラメタで渡されてきたInjectのinjメソッドを使ってcase class(PrintLine, ReadLine)をインジェクト可能にしたもの(?)をFree.liftFCでOperationalモナド化しています。

コンパニオンオブジェクトの方にはおまじないの暗黙変換関数を定義しています。

AuthService

次にConsoleServiceに合成して使用するOperationalモナドとしてAuthOperationをAuthServiceに定義します。

Operationalモナド化のターゲットとなる1階カインド型のトレイトとしてAuthOperationを、具体的なコマンドとなるcase classとしてLoginを定義してます。

インタープリターの実行エンジンはinterpreterとして定義しています。

その後ろにあるAuthPartクラスとコンパニオンオブジェクトがOperationalモナド合成のために必要な定義です。

package sample

import scala.language.higherKinds
import scalaz._, Scalaz._

object AuthService {
  sealed trait AuthOperation[_]
  case class Login(user: String, password: String) extends AuthOperation[Unit]

  val interpreter = new (AuthOperation ~> Id) {
    def apply[T](c: AuthOperation[T]): Id[T] = {
      c match {
        case Login(login, password) => println(s"$login:$password")
      }
    }
  }

  class AuthPart[F[_]](implicit I: Inject[AuthOperation, F]) {
    def login(user: String, password: String): Free.FreeC[F, Unit] = Free.liftFC(I.inj(Login(user, password)))
  }

  object AuthPart {
    implicit def instance[F[_]](implicit I: Inject[AuthOperation, F]): AuthPart[F] = new AuthPart[F]
  }
}

この例では、プログラムの意図を見やすくするためAuthService本体ではユーティリティ関数を定義していませんが、実応用時にはAuthService本体とAuthPartの両方で定義して、合成非使用時、合成使用時のどちらのケースでも動作可能にしておくことになります。

Utility

Operationalモナド合成のために必要なユーティリティ関数orを定義します。これは「CoproductとInjectを使ったFree Monadの合成とExtensible Effects」にある関数をそのまま持ってきています。

package sample

import scala.language.higherKinds
import scalaz._, Scalaz._

object Utility {
  def or[F[_], H[_], G[_]](
    fg: F ~> G, hg: H ~> G
  ): ({ type f[x] = Coproduct[F, H, x]})#f ~> G = {
    new (({type f[x] = Coproduct[F,H,x]})#f ~> G) {
      def apply[A](c: Coproduct[F,H,A]): G[A] = c.run match {
        case -\/(fa) => fg(fa)
        case \/-(ha) => hg(ha)
      }
    }
  }
}

2つの自然変換(NaturalTransformation)を合成した自然変換を作成する関数のようです。

積(product)の双対であるCoproduct(余積)は直和と同等ということらしく、型レベルのEither(Disjoint union)と考えてよさそうです。Scalazの内部実装もScalazの「\/」を使っています。

合成後の自然変換はCoproductとして渡されてきた型が合成された2つの自然変換のどちらに該当するかを判定して、該当する自然変換によって変換を行う、というロジックだと思います。

ConsoleAuthService

さていよいよ合成です。

ConsoleServiceとAuthServiceを合成したConsoleAuthServiceを定義してみました。

package sample

import scalaz._, Scalaz._

object ConsoleAuthService {
  import ConsoleService._, AuthService._

  type ConsoleAuth[A] = Coproduct[ConsoleOperation, AuthOperation, A]
  val interpreter = Utility.or(ConsoleService.interpreter, AuthService.interpreter)
  def run[T](f: Free.FreeC[ConsoleAuth, T]) = Free.runFC(f)(interpreter)
}

定義はとても簡単で以下の3行だけです。

  • CoproductによるOperationalモナドの合成
  • 実行エンジンの合成
  • runメソッド
CoproductによるOperationalモナドの合成

Coproductを使って2つのOperationalモナド(ConsoleOperation, AuthOperation)を合成します。

type ConsoleAuth[A] = Coproduct[ConsoleOperation, AuthOperation, A]

正確には、2つのOperationalモナドのいずれかを保持するCoproductを定義する、ということになろうかと思います。

実行エンジンの合成

Utilityのor関数でConsoleServiceの実行エンジンとAuthServiceの実行エンジンを合成します。実行エンジンは自然変換なので、汎用の自然変換の合成機能で合成することができます。

val interpreter = Utility.or(ConsoleService.interpreter, AuthService.interpreter)

意味的には、「2つのOperationalモナドのいずれかを保持するCoproduct」をターゲットの関手に変換する自然変換、ということになろうかと思います。

runメソッド

runメソッドでは、「2つのOperationalモナドをCoproductを使って合成したもの」をFreeモナド化したものをパラメタで受取り、「2つのOperationalモナドをCoproductを使って合成したもの」の実行エンジンをFree.runFC関数で適用して処理を実行しています。

def run[T](f: Free.FreeC[ConsoleAuth, T]) = Free.runFC(f)(interpreter)

App

2つのOperationalモナドを合成したConsoleAuthServiceの使用方法は以下になります。

package sample

import scala.language.higherKinds
import scalaz._, Scalaz._

object App {
  import ConsoleService.ConsolePart, AuthService.AuthPart

  def program[F[_]](implicit C: ConsolePart[F], A: AuthPart[F]) = {
    import C._, A._
    for {
      password <- readLine
      _ <- login("user", password)
    } yield ()
  }

  def main(args: Array[String]) {
    ConsoleAuthService.run(program)
  }
}

program関数の実装はConsoleServiceやAuthServiceを単独で使う場合と同様にfor式によるものですが以下の点が異なっています。

  • 暗黙パラメタとしてscalaz.Injectを使った合成用のクラスConsolePartとAuthPartを受け取っている。
  • ConsoleServiceとAuthServiceのユーティリティ関数として暗黙パラメタで受け取ったクラスのメソッドを使用している。

このあたりをおまじないとして考えると、ConsoleServiceやAuthServiceを単独で使う場合とほとんど変わらない手間で、ConsoleServiceとAuthServiceを合成したConsoleAuthServiceを使用できています。

まとめ

Scalaz 7.1.0でOperationalモナド(Freeモナド)の合成が簡単にできることが確認できました。

モナドを合成できるのもよいですが、モナドの実行エンジンも自然変換を使って簡単に合成できるのも好感触です。

今まではモナドは合成できないためモナド変換子(e.g. WriteT)を都度作るアプローチだったと思いますが、Operationalモナド/Freeモナドを使うことで合成によるアプローチを取ることも可能になったと理解してよいのかもしれません。

どこまで実用性があるかは未知数ですが、この検証も含みにアプリケーションやフレームワークで定義するモナドはOperationalモナドを第1候補に考えてみたいと思っています。

諸元

  • Scala 2.11.4
  • Scalaz 7.1.0

2015年1月8日木曜日

Operationalモナド

おぼろげな記憶ではFreeモナドが話題になったのが一昨々年、Operationalモナドは一昨年だったと思うので、いまさらの感もありますが、ScalazでOperationalモナドが簡単に使えることが判明したのでメモしておきます。

Freeモナドは、FunctorをMonad化することができるモナドですが、以下の性質を持ったインタープリターのDSLを簡単に作れることで知られています。

  • トランポリン化によりjoin演算時の再帰呼び出しによるスタックオーバーフローを回避
  • インタープリターの実行エンジンが疎結合になっており取替え可能
  • 副作用を実行エンジン側に分離(2015-01-14追記)

Operationlモナドは、Coyonedaにより型パラメータが1つの1階カインド型のデータ(Scala的にはcase classで実装。以下case class。)をFunctor化する事が可能な性質を利用して、case classから直接Freeモナド化したものです。

ざっくりいうとcase classでインタープリターのオペレーションを定義すれば、ここから簡単にインタープリターの(monadicな)DSLを作ることができるわけです。

プロダクトではScalaz 7.0.6を使っているのですが、このバージョンでOperationalモナドを使おうとするとかなり込み入った呪文を唱えないといけません。

Operationalモナドは非常に便利そうなんですが、呪文が必要になるとすると、あまり気軽に使えなくなってしまいます。

この問題がScalaz 7.1.0で解消されていることが判明したわけです。(Scalaz 7.1.0のリリースが昨年8月のようなのでいまさらですが。)

ConsoleService

Scalaz 7.1.0の機能を使用したOperationalモナドの実装は以下になります。

package sample

import scalaz._, Scalaz._
import scalaz.concurrent.Task

object ConsoleService {
  sealed trait ConsoleOperation[_]
  case class PrintLine(msg: String) extends ConsoleOperation[Unit]
  case object ReadLine extends ConsoleOperation[String]

  def printLine(msg: String) = Free.liftFC(PrintLine(msg))
  def readLine: Int = Free.liftFC(ReadLine)

  val interpreter = new (ConsoleOperation ~> Id) {
    def apply[T](c: ConsoleOperation[T]): Id[T] = {
      c match {
        case PrintLine(msg) => println(msg)
        case ReadLine => scala.io.StdIn.readLine()
      }
    }
  }

  def run[T](f: Free.FreeC[ConsoleOperation, T]): T = {
    Free.runFC(f)(interpreter)
  }

  val interpreterTask = new (ConsoleOperation ~> Task) {
    def apply[T](c: ConsoleOperation[T]): Task[T] = {
      Task.delay {
        c match {
          case PrintLine(msg) => println(msg)
          case ReadLine => scala.io.StdIn.readLine()
        }
      }
    }
  }

  def runTask[T](f: Free.FreeC[ConsoleOperation, T]): Task[T] = {
    Free.runFC(f)(interpreterTask)
  }
}
case classの定義

まずインタープリターのコマンドとなるcase classを定義します。

ここではコンソールに文字列を出力するPrintLineとコンソールから文字列を入力するReadLineを定義しています。この2つのcase classの親クラスとしてConsoleOperationトレイトを定義しています。このConsoleOperationトレイトが型パラメータを1つ取る構造になっているためCoyonedaによるOperationalモナド化が可能になっています。

定義時のイメージとしてはcase classの引数にコマンドの引数、case classの親クラスであるConsoleOperationの型パラメータにコマンドの返り値を定義します。

sealed trait ConsoleOperation[_]
  // PrintLine(msg: String): Unit
  case class PrintLine(msg: String) extends ConsoleOperation[Unit]
  // ReadLine(): String
  case object ReadLine extends ConsoleOperation[String]
ユーティリティ関数

次にFreeモナドとして使いやすくするためのユーティリティ関数を定義します。

定義は簡単で、scalaz.FreeのliftFC関数に(条件に合致した)case classを渡すだけです。

Scalaz 7.0.6ではこのliftFC関数がなかったために呪文が必要だったわけです。

def printLine(msg: String) = Free.liftFC(PrintLine(msg))
  def readLine: Int = Free.liftFC(ReadLine)
インタープリター

ConsoleServiceの利用時には、Freeモナドの内部構造としてPrintLineやReadLineの列が構築されますが、これを解釈して実行するインタープリターを定義します。

これはScalazのNaturalTransformation(自然変換)を用いて実装します。NaturalTransformation自体は1階カインド型間の変換(圏論的には関手間の射?)の機能ですが、ここではモナド間の変換に適用します。

まずConsoleOperationモナド(Operationalモナド化したもの)をIdモナドに変換する自然変換によるインタープリターの実行エンジンです。

val interpreter = new (ConsoleOperation ~> Id) {
    def apply[T](c: ConsoleOperation[T]): Id[T] = {
      c match {
        case PrintLine(msg) => println(msg)
        case ReadLine => scala.io.StdIn.readLine()
      }
    }
  }

Idモナドは実行結果をモナドにくるまず直接取得する時に使用するモナドです。

変換先としてIDモナドを指定することで、インタープリターの実行結果の値を取得できるようになります。

実行ユーティリティ

NaturalTransformationとして実装されたインタープリターはscalaz.FreeのrunFC関数で実行することができます。

Scalaz 7.0.6ではこのrunFC関数もなかったために、ここでも呪文が必要でした。

def run[T](f: Free.FreeC[ConsoleOperation, T]): T = {
    Free.runFC(f)(interpreter)
  }
Task版インタープリターと実行ユーティリティ

scalaz.concurrent.Taskは、scala.util.Tryとscala.concurrent.Futureの機能を併せ持ったようなモナドです。例外のキャッチ、遅延評価、並列実行といった目的で汎用的に使用することができます。

scala.util.Tryとscala.concurrent.FutureはScalaz 7.1.0的にはモナドとして定義されていないので、Monadicプログラミングをする上ではTaskの方が都合がよいです。

ここではFreeモナドConsoleOperationをTaskに変換するインタープリターを実装しました。正確にはインタープリターの実行を行う関数が束縛されたTaskが返ってきます。

実行はID版と同様にscalaz.FreeのrunFC関数を用います。

val interpreterTask = new (ConsoleOperation ~> Task) {
    def apply[T](c: ConsoleOperation[T]): Task[T] = {
      Task.delay {
        c match {
          case PrintLine(msg) => println(msg)
          case ReadLine => scala.io.StdIn.readLine()
        }
      }
    }
  }

  def runTask[T](f: Free.FreeC[ConsoleOperation, T]): Task[T] = {
    Free.runFC(f)(interpreterTask)
  }

Operationalモナドを使う

OperationalモナドによるConsoleServiceの使用方法は以下になります。

package sample

import ConsoleService._

object App {
  def main(args: Array[String]) {
    console_id
    console_task
  }

  def console_id {
    val program = for {
      msg <- readLine
      _ <- printLine(msg)
    } yield Unit
    run(program)
  }

  def console_task {
    val program = for {
      msg <- readLine
      _ <- printLine(msg)
    } yield msg
    val task = runTask(program)
    task.run
  }
}
IDモナド

console_idメソッドではConsoleServiceのrunメソッドを使用して、プログラムの実行を行います。IDモナドを使っているため、(処理を行う関数が束縛された)モナドが返されるのではなく処理が直接実行されます。

Taskモナド

console_taskメソッドではConsoleServiceのrunTaskメソッドを使用してプログラムの実行を行います。

runTaskメソッドは処理を行う関数が束縛されたTaskモナドを返すので、さらにrunメソッドでプログラムの実行を行います。

まとめ

Operationalモナドは敷居が高いので今まで使っていなかったのですが、Scalaz 7.1.0で簡単に利用できるようになっているのが分かりました。

プロダクトで使っているライブラリのバージョンを上げるのは勇気が要るところですが、Operationalモナド目的で、そろそろ上げておくのがよさそうです。

2014年7月28日月曜日

Scalaz勉強会しました

APPAREL CLOUDアパレルウェブが提供しているアパレル向けのクラウドサービスです。ボクが所属するEverforthの提供するEverforthプラットフォーム上に構築されています。
Everforthプラットフォームは、O2Oやオムニチャネル向けのクラウド・プラットフォームです。Everforthプラットフォームでは、サービスを実行するクラウド実行基盤と同時にO2Oやオムニチャネル・ビジネスをサービスに結び付けるための業務分析、システム分析を包含したサービス開発体系の提供も予定しています。
「サービス開発体系」は、オブジェクト指向ビジネス・モデリング、オブジェクト指向分析設計の技術をベースに、Everforthプラットフォームをターゲットとしたアプリケーション開発の方法論として整備していく予定です。
一般的なオブジェクト指向分析設計方法論が複雑で分かりづらいものになっているのは、どの業務分野、どの実装技術、どの実行基盤に対してもニュートラルな形で整備されているため、(1)汎用性を担保するために複雑化する、(2)ターゲットの業務分野、実装技術、実行基盤むけにカスタマイズが必要、という要因があると思います。
逆に、業務分野、実装技術、実行基盤を確定した上でカスタマイズすればかなりスリムなものにできるはずです。
Everforthサービス開発体系では、業務分野はO2O/オムニチャネル、(バックエンドの)実装技術はScalaによるOFP(Object-Functional Programming)、実行基盤はEverforthクラウドプラットフォームとすることで、この問題を解決しようとしています。

勉強会

Everforthサービス開発体系を業務に載せるためには、エンジニアの技術教育が非常に重要になってきます。
そこで上流のビジネス・モデリングからScalaでの実装にいたるまでの一連の技術の入門編を企画しました。これらの技術は連動しており、業務分析からオブジェクト指向分析/設計を経由してScalaでの実装まで一気通貫の体系になっています。
  • 実務者のためのかんたんScalaプログラミング
  • 実務者のためのかんたんScalaz(第1回)
  • 実務者のためのかんたんScala入出力プログラミング
  • 実務者のためのかんたんScala設計
  • 実務者のためのかんたんオブジェクト指向分析/設計
  • 実務者のためのかんたん業務分析
第1回は7月7日に札幌で開催しました。
札幌が東京と並んでEverforthの開発拠点になっているので、まず札幌で勉強会として開始しています。
今回は技術体系をざっくりおさらいするのが目的で、Eveforthプラットフォームに依存した部分もないので、オブジェクト指向技術やScalaの普及という目的もあり、一般の方も参加できる形にしています。

Scalaz

第1回「実務者のためのかんたんScalaz」の資料は以下になります。


最初のテーマとしてScalazを選んだのは、社内チャットでScalazについての質問があったのがきっかけになっていますが、関数型プログラミング導入の狙いの説明を最初にしておくのが有益ではないかという判断もあります。Scalazは「純粋」関数型プログラミングのためのライブラリなので、関数型プログラミングの方向性や特性をより際立った形で取り上げることができます。
かんたん?
今回の資料で「かんたん」なのは、スライドの右上に「かんたん」のマークがついている所で、以下の2つの要素です。
  • Scalazの便利機能
  • Monoid
それ以外は、なんとなく聞いておいてもらえばよい、というレベルの情報として考えています。
逆に「Monoid」についてはこの機会にプログラミングに取り入れて欲しい、という意図から「かんたん」の範囲に含めています。「Monoid」というととっつきにくく感じますが、プログラミングのイディオムとしては簡単で利用範囲の広い汎用テクニックであり、関数型プログラミングの考え方をマスターする起点にもなるという、一粒で二度も三度もおいしいものです。
Scalazを本格的に取り上げる場合は、Traversable・Foldable、Promise・Task、Free、Treeといった所もテーマにしたいのですが、今回は「かんたん」ということで断念しました。機会があればこの辺りも加味した、Object-Function Programmingという切り口でまとめてみたいと思います。

次回以降の予定

第2回は「実務者のためのかんたんScala設計」を札幌で予定しています。多分9月の初旬頃になると思います。
東京(or 横浜)でも準備ができしだい開催したいと思います。

2014年7月3日木曜日

関数型プログラミング技術マップ2014

来週の月曜(7月7日)に札幌で「実務者のためのかんたんScalaz」という勉強会を予定しています。
この勉強会は、クラウドアプリケーション開発の基本技術の情報展開を目的としたもので、以下の勉強会をシリーズで行っていく予定です。
  • 実務者のためのかんたんScalaプログラミング
  • 実務者のためのかんたんScala入出力プログラミング
  • 実務者のためのかんたんScala設計
  • 実務者のためのかんたんオブジェクト指向分析/設計
クラウドアプリケーションの開発技法を整備する上では、業務アプリケーション開発の基盤技術であるオブジェクト指向分析/設計、オブジェクト指向プログラミングの土台の上に関数型言語をどうのように接合していくのか、という点が焦点の一つと思います。
この議論を深めるためには、関数型言語の技術的な背景、文脈を整理することが必要ということで2012年に「関数型言語の技術マップ」を作成しました。
今回、勉強会向けにこの技術マップを改良してみました。
改良点は以下の2つです。
  • 純粋関数型言語の性質
  • モナドと圏論の位置付け

純粋関数型言語の性質

純粋関数型言語の性質として以下のものを参考に補足しました。
  • 不変 (immutable)
  • 副作用なし (no side effect)
  • 参照透過性 (referential transparency)
  • 置換モデル (substitution model)

モナドと圏論の位置付け

圏論の入門書を幾つかチェックしましたが、モナドについてはほとんど記載がなく、モナドは圏論の基本概念ではないらしいということが分かりました。
また、"圏論の中に(基本構成要素として?)モナドがあるのは違和感がある"というコメントもいただき、技術マップの中で圏論やモナドをどのように位置付けるかが懸案事項になっていました。
その後、色々と調べてみて分かったのですが、圏論については、種々存在する数学や情報科学の各分野を統一的に記述するための「普遍言語」と考えるのが適切のようです。
代数的構造だけでなく、プログラミング言語の構造や論理学も圏論で記述することが可能という関係になります。図にはこの理解を反映しました。
たとえば、位相空間の圏であるトポスで数理論理学のモデルを記述することができるようです。またプログラミング言語の圏としてHaskellのHask圏が有名です。
その上で「モナド(monad)」ですが、これは「プログラミング言語の圏で有効な概念」でこれを情報科学的に記述する時に圏論を用いる、という関係と考えるとよさそうです。「プログラミング言語の圏で有効な概念」なので、圏論を知らなくてもプログラミングに便利に使える概念のはずです。
圏論もモナドも難解な概念であるため、クラウドアプリケーションの開発者がどこまで理解しておく必要があるのか、という点が重要な論点になります。
この2つの概念を黒帯レベルで理解することが必須ということになると、現場への展開は事実上不可能になってしまいます。とはいえ全く知らないで良いということでもないと思うので、その線引が非常に重要になってきます。
勉強会では、ボクがこの辺りがよいのではと思っているラインについて、Scala言語での具体的なアプローチ方法も含めて、私案を展開したいと思います。

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