2012年3月12日月曜日

関数型とデータフロー(1)

要求開発アライアンスのセッション『Object-Functional Analysis and Design: 次世代モデリングパラダイムへの道標』で使用するスライドについて背景説明を行っています。

今回は背景説明第9弾として、「関数型とデータフロー(1)」として用意した以下の図を説明します。

オブジェクト・モデリングと関数型の連携ではデータフロー・モデルが重要な位置付けにあることを説明してきました。そして、前回「データフローの実装技術」では具体的な実装技術について取り上げました。

関数型言語でデータフロー・モデルを扱うための要素技術として、関数型言語上でデータフローをどのように記述するのかという記述方式が論点の一つとなります。

関数型言語上でデータフローモデルを記述する絶対確実な方法としてデータフローを構成する各ノードとノード間のリンクを集合的に記述する方法がありますが、可読性はあまりよくないので、あくまでも最終手段として考えておきたいところです。

そういう意味で、前回紹介したSparkでの以下の記述は、関数型言語として自然であり、とても魅力的です。

val file = spark.textFile("hdfs://...")
 
file.flatMap(line => line.split(" "))
    .map(word => (word, 1))
    .reduceByKey(_ + _)

この記述は、以下の処理をデータフロー的に繋ぐ表現を行っています。

  • テキストデータを各行毎に空白を区切り記号にして単語に分割
  • 書く単語毎に単語とカウンタの対のデータ構造に変換
  • 単語毎に値を加算で畳込み

このデータフローの実行の結果、テキストファイル内に存在する単語の単語数の一覧を得ることができます。

関数型言語を使うと、このように関数型言語的にもデーターフロー的にも自然な記述が可能になりそう、ということが分かりました。そこで、今回から数回に分けてScalaを例に関数型言語でデータフローを記述する方式について考えていきます。(セッションでは、ここで考えた内容を1,2枚のスライドに圧縮して説明することになると思います。)

基本

今回の図は、関数型言語でデータフローを記述する方法を考える基本形です。

まず、関数オブジェクトplus5とmul10を定義します。plus5は引数に5を加えたInt型の値を返す関数、mul10は引数に10を掛けたInt型の値を返す関数です。

val plus5 = (_: Int) + 5
val mul10 = (_: Int) * 10

この2つの関数を使ってInt型に5加え、さらに10を掛けるデーターフローについて考えます。このデータフローにInt値3を渡すと80が返ってきます。

普通の記述

この演算を普通に記述すると以下のようになります。これは、関数型言語でも手続き型言語でも共通のごく普通の記述方式です。ここでは関数呼び出し方式と呼ぶことにします。

mul10(plus5(3))

このように考えてみると、この基本的な記述方式も一種のデータフローと考えることができます。ただし、図で示したデータフローと記述方式のマッピングが直感的には分からないという問題があります。

関数合成

関数型言語では、関数合成によって複数の関数を合成して新しい関数を定義することができます。

以下では、関数オブジェクトのcomposeメソッドとandThenメソッドで関数合成しています。

(mul10 compose plus5)(3)
(plus5 andThen mul10)(3)

関数合成は、関数オブジェクトをプログラム的に操作して柔軟な結合ができるようになるのが魅力ですが、データフローの記述に用いるには、記述が冗長なのと、データフロー的な意味での可読性はあまり高くありません。

Scalaz

Scala用のクラスライブラリScalazを用いると、以下のような記述が可能になります。

3 |> plus5 >>> mul10

データフローの左側からInt値3が右側の方向に流れていくことが直感的に分かる記述方式になっています。

これは一種のDSLといえますが、Scalaの関数呼び出しに型クラスのメカニズムを用いて文法糖衣的な皮をかぶせているだけで、内部的にフレームワーク的な新しい演算メカニズムを導入しているわけではありません。つまり、関数型言語の持つデータフロー的なセマンティクスを活かす表現方式となっているわけです。

0 件のコメント:

コメントを投稿