2012年7月31日火曜日

クラウド温泉3.0 (10) / map, filter, fold

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」で使用するスライドのネタ検討その10です。

パイプライン・プログラミングを構成する要素として以下の2つを説明しました。

Functorが提供する計算文脈におけるパイプラインでは、一般的にmapメソッドに加えてfilterメソッドとfoldメソッドを使うことができます。たとえばListでは以下のようになります。

scala> List(1, 2, 3).filter(_ % 2 == 1).map(_ * 2).fold(0)(_ + _)
res3: Int = 8

このようにメソッドをつないでパイプラインを構築することができるわけです。map, filter, foldメソッドは基本中の基本部品となっています。

for式

mapメソッドとfilterメソッドはfor式の文法糖衣も用意されています。文法糖衣が用意されていることからも基本中の基本であることがわかります。

上記の式の前半は以下になります。

scala> List(1, 2, 3).filter(_ % 2 == 1).map(_ * 2)
res5: List[Int] = List(2, 6)

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

scala> for (x <- List(1, 2, 3) if x % 2 == 1) yield x
res4: List[Int] = List(1, 3)

パイプラインの構成部品

map, filter, foldメソッドをパイプラインの構成部品と考えて、パイプラインの観点から整理してみました。

メソッド動作動作イメージコンテナ要素要素数
mapコンテナ上の要素に関数を適用して新しいコンテナに詰め直す。M[A]→M[B]変わらない変わる変わらない
filterコンテナ上の要素を選別して新しコンテナに詰め直す。M[A]→M[A]変わらない変わる減る
foldコンテナをまるごと別のオブジェクトに変換する。M[A]→N変わるなくなるなくなる

説明の都合上コンテナという用語を導入しました。コンテナは計算文脈の実装と考えてもよいですし、パイプライン上に要素を載せて流れる荷車というようにイメージしてもよいでしょう。

このように整理することでパイプラインの構成部品を分類するための軸として以下のものを抽出することができました。

  • コンテナの型の変換 (選択肢: 変わる、変わらない)
  • 要素の型の変換 (選択肢: 変わる, なくなる)
  • 要素数の変化 (選択肢: 変わらない, 減る, なくなる)

バリエーションの組み合わせ的にはmap, filter, foldの3種類以外にも色々と有用な組み合わせがありそうです。

fold

foldメソッドは、コンテナをまるごと別のオブジェクトに変換するので、パイプラインの終端ということになります。

foldメソッドは色々なバリエーションがあります。以下の記事が参考になると思います。

2012年7月30日月曜日

クラウド温泉3.0 (9) / Functorによる計算文脈

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」で使用するスライドのネタ検討その9です。

前回はListを素材にパイプラインの重要な構成要素であるFunctorを取り上げました。

List処理でのmapメソッドは関数型プログラミングの定番中の定番なので、List操作あるいはコレクション操作でのmapメソッドの効用はよく知られていると思います。しかし、mapメソッドをFunctorの観点からみると、「計算文脈」というもっと重要な概念が隠れています。

Option

JavaからScalaに入った当初はOptionはnullの問題を回避するためのオブジェクトで"要素数1の劣化版コレクション"というようなイメージを持つかもしれません。Optionは格納する要素数が1つなので、Listでは有用であったmapメソッドがあってもあまりメリットがあるように感じないでしょう。そもそも存在に気づかないかもしれません。

Optionは「nullの問題を回避するためのオブジェクト」というような脇役の存在ではなく、Monadicプログラミングの花形オブジェクトの一つです。それは、Optionが「計算文脈」を提供するからです。

前回のListの例をOptionに置き換えたものは以下になります。要素数が1なのであまり面白みがありません。

scala> Some(1).map(_ * 3).map(_ + 5)
res7: Option[Int] = Some(8)

scala> Some(1).map(mul3).map(plus5)
res8: Option[Int] = Some(8)

scala> Some(1).map(mul(3, _)).map(plus(5, _))
res9: Option[Int] = Some(8)

scala> Some(1).map(mulc(3)).map(plusc(5))
res10: Option[Int] = Some(8)

ところがOptionがNoneだった場合は話が変わります。OptionがNoneの場合にはmapメソッドで指定した計算がなんであっても答えは必ずNoneになります。

scala> none[Int].map(_ * 3).map(_ + 5)
res14: Option[Int] = None

scala> none[Int].map(mul3).map(plus5)
res15: Option[Int] = None

scala> none[Int].map(mul(3, _)).map(plus(5, _))
res16: Option[Int] = None

scala> none[Int].map(mulc(3)).map(plusc(5))
res17: Option[Int] = None

Optionを「成功と失敗」の計算文脈と考えると、この挙動の意味が分かってきます。OptionがSomeの間はOptionの計算文脈は「成功」でmapメソッドによる計算結果は計算文脈上つまりOption上に残ります。一方、OptionがNoneになるとOptionの計算文脈は「失敗」でmapメソッドによる計算結果は必ずNoneつまり「失敗」になります。

Optionの計算文脈に関する議論は以下の記事が参考になると思います。

その他の計算文脈

また、EitherやValidationの計算文脈としての意味、使用方法は以下の記事が参考になると思います。

計算文脈

計算文脈の観点からパイプライン演算やデータフローを理解するには以下の記事が参考になると思います。

この記事は「Object-Functional Analysis and Design: 次世代モデリングパラダイムへの道標」の一環で、最後のまとめは「Object-Functional Analysis and Designまとめのまとめ」に、続編は「Object-Functional Analysis and Designふたたび」なります。

あくまで私案ですが、ソフトウェア開発方法論におけるデータフロー、パイプライン演算、計算文脈、モナド(ここまでの記事には出てきていませんが)の位置付けを考える上でのヒントになると思います。

ノート

Monadicプログラミングの重要な概念である計算文脈は説明が難しいので、スライドでどうしていくのか要検討という感じです。このあたりはプログラムをたくさん書いて体で覚えていくしかないかもしれません。スライドでは簡単な説明をした後、参考リンクを紹介という形になりそうです。

2012年7月27日金曜日

クラウド温泉3.0 (8) / Functor

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」で使用するスライドのネタ検討その8です。

前回は関数を用いたMonadicプログラミングについて考えました。今回はFunctorを用いたMonadicプログラミングについて考えます。

前回、パイプライン・プログラミングを広義のMonadicプログラミングと定義しました。

今回は、FunctorによるMonadicプログラミングを考えます。FunctorはMonadではありませんが、パイプライン演算に見立てることができます。

mapメソッド

Listなどのコレクションクラスが提供するmapメソッドはコンテナに格納されているオブジェクトに対して計算した結果得られた新たなオブジェクトに詰めなおしたコンテナを生成します。このようなmapメソッドを持つオブジェクトを一般的にFunctorと呼びます。ScalaとFunctorの関係は「Scala で圏論入門」に詳しいです。

mapメソッドの使い方は以下になります。

scala> List(1, 2, 3).map(x => x * 3)
res1: List[Int] = List(3, 6, 9)

mapメソッドをつなぐことでパイプライン処理を記述することができます。

scala> List(1, 2, 3).map(x => x * 3).map(x => x + 5)
res2: List[Int] = List(8, 11, 14)

Scalaでは関数リテラルでの省略記法が提供されているので、これを用いると以下のように書くことができます。 

scala> List(1, 2, 3).map(_ * 3).map(_ + 5)
res3: List[Int] = List(8, 11, 14)

関数

mapメソッドに直接関数リテラルで処理を記述してもよいのですが、再利用可能な部品として関数を用意しておいて、これを指定する使い方がより望ましいでしょう。

関数mul3とplus5を用意します。いずれも引数の数が1つの関数です。

def mul3(a: Int) = a * 3
def plus5(a: Int) = a + 5

これをmapメソッドで使うと以下になります。

scala> List(1, 2, 3).map(x => mul3(x)).map(x => plus5(x))
res11: List[Int] = List(8, 11, 14)

Scalaでは関数リテラルでの省略記法が提供されているので、これを用いると以下のように書くことができます。 こうなるとパイプラインに部品を接続することで処理を組み立てていることが明確になります。

scala> List(1, 2, 3).map(mul3).map(plus5)
res4: List[Int] = List(8, 11, 14)

部分適用

引数の数が2つ以上の関数では部分適用を用いることで、mapによるパイプラインに適用できます。

関数の引数が複数あるケースを考えます。関数mulとplusを用意します。いずれも引数の数が2つの関数です。

def mul(a: Int, b: Int) = a * b
def plus(a: Int, b: Int) = a + b

これをmapメソッドに適用すると以下になります。

scala> List(1, 2, 3).map(mul(3, _)).map(plus(5, _))
res5: List[Int] = List(8, 11, 14)

カリー化

関数がパイプラインの中で使われることが明らかな場合はカリー化しておくと便利です。カリー化した関数pluscとmulcは以下になります。

def mulc(a: Int)(b: Int) = a * b
def plusc(a: Int)(b: Int) = a + b

pluscとmulcを用いてパイプラインを構築すると以下になります。

scala> List(1, 2, 3).map(mulc(3)).map(plusc(5))
res6: List[Int] = List(8, 11, 14)

ノート

広義のMonadicプログラミングをパイプライン・プログラミングと定義し、関数によるパイプライン・プログラミング、Functorによるパイプライン・プログラミングについてみてきました。

他に幾つかパイプライン・プログラミングの候補があるので、次回以降に取り上げる予定です。

スライドでは、パイプライン・プログラミングの実現技術をリストアップした後、これを一つにまとめてプログラミング・スタイルとして整備したいと考えています。

2012年7月26日木曜日

クラウド温泉3.0 (7) / 関数によるMonadicプログラミング

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」で使用するスライドのネタ検討その7です。

前回はMonadicプログラミングの非公式定義を考えました。まとめると以下になります。

Monadを中心としつつも、旧来からあるFunctorによるプログラミング、関数合成、コンビネータなども包含しつつ、パイプライン・プログラミングのスタイルとしてまとめたもの。

今回は、普通の関数呼出しによるMonadicプログラミングを考えます。普通の関数呼び出しなので当然モナドは使っていません。つまりパイプライン・プログラミングとしての関数呼び出しです。

パイプライン演算子

関数mul3とplus5を用意します。いずれも引数の数が1つの関数です。

def mul3(a: Int) = a * 3
def plus5(a: Int) = a + 5

これを普通に関数呼び出しで使うと以下のようになります。このように引数が一つの関数を連続的に呼び出すケースはパイプライン処理に見立てることができます。

scala> mul3(plus5(10))
res71: Int = 45

Scalazでは関数呼出しによるパイプライン処理を記述するための演算子として「|>」を提供しています。パイプライン演算子と呼ばれています。

このパイプライン演算子を使って上記処理を記述すると以下になります。まさにパイプライン演算ですね。

scala> 10 |> plus5 |> mul3
res73: Int = 45

複数の引数

関数の引数が複数あるケースを考えます。関数mulとplusを用意します。いずれも引数の数が2つの関数です。

def mul(a: Int, b: Int) = a * b
def plus(a: Int, b: Int) = a + b

これを普通に関数呼び出しで使うと以下のようになります。

scala> mul(3, plus(5, 10))
res74: Int = 45

ここで、この演算をScalazのパイプライン演算子で記述しようとすると、はたと困ってしまいます。これは、パイプラインのセマンティクスより、引数を複数持つ関数の呼出しの方がセマンティクスが大きいからです。より汎用的なわけですね。

関数呼び出しは、関数の実行結果をルートとする木構造として考えることができます。ルートからリーフに至る複数あるパスのそれぞれはパイプラインとして考えることができるので、複数のパイプラインを合成したものと考えることができます。

パイプラインのセマンティクスに持ち込むためには、複数あるパスの中の1つを主のパスと定め、このパスを中心にパイプラインを記述していく必要があります。

これを関数の呼び出しで実現するには、上記の複数引数による関数呼び出しを引数の数が1つの関数の関数呼び出しに変換します。なぜなら、関数の結果はひとつなので、これを関数がそのまま受け取るためには引数の数が1つでなければなりません。つまり、引数の数が1つの関数を用意し、これをパイプラインとして結合して動作させることになります。

部分適用

Scalaは関数の引数を部分的に適用した新しい関数を作成する、部分適用という機能を提供しています。部分適用を用いて記述すると以下になります。

scala> 10 |> (plus(5, _: Int)) |> (mul(3, _: Int))
res83: Int = 45

カリー化

関数がパイプラインの中で使われることが明らかな場合はカリー化しておくと便利です。カリー化した関数pluscとmulcは以下になります。

def mulc(a: Int)(b: Int) = a * b
def plusc(a: Int)(b: Int) = a + b

pluscとmulcを用いてパイプラインを構築すると以下になります。随分読みやすくなりました。この例からも分かるようにカリー化のコツは、パイプライン上で受け渡される引数を最後に持ってくることです。

scala> 10 |> plusc(5) |> mulc(3)
res78: Int = 45

便利かどうかは別として、curriedメソッドを用いて関数をその場でカリー化して使用することもできます。

scala> 10 |> (plus _ curried(5)) |> (mul _ curried(3))
res92: Int = 45

2012年7月25日水曜日

クラウド温泉3.0 (6) / Monadicプログラミングの非公式定義

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」で使用するスライドのネタ検討その5です。

本ブログでは「Monadicプログラミング」という用語を使っていますが、Monadicプログラミングやmonadic programmingという用語をウェブで検索しても、ピッタリとした定義は見つかりません。

だいたいMonadを使ったプログラミング・スタイルというのが、最大公約数的な意味のようです。

ただ、Monadの使用の有無をプログラミング・スタイルのスコープにするとMonadを使わないケースは適用範囲外ということになり、適用できるユースケースが限定されます。

そこで、Monadを中心としつつも、旧来かあるFunctorによるプログラミング、関数合成、コンビネータなども包含しつつ、「ある種のプログラミング・スタイル」としてまとめたものを、本ブログおよびクラウド温泉のセッションではMonadicプログラミングと呼ぶことにしたいと思います。

そこで「ある種のプログラミング・スタイル」ですが、パイプライン・プログラミング、パイプラインによって部品を結合したものの上にデータを流していく、というスタイルを指すことにします。

たとえば、「Object-Functional Analysis and Design - 次世代プログラミングパラダイムを考える」の以下のスライドでみられるようなプログラミング・スタイルですね。



この中の後者のパイプラインはMonadは使わずArrowを使っているので、Monadに限定するとスコープに入ってきません。こういったスタイルを包含するためにMonadicプログラミングの意味をMonadを中心としたパイプライン・プログラミングに拡張したいわけです。

Monadを中心としたパイプライン・プログラミングに関する上記スライドに関係して、以下の記事が参考になると思います。このあたりがMonadicプログラミングの典型的な使用例になります。

2012年7月24日火曜日

クラウド温泉3.0 (5) / 永続データ構造としてのケースクラス

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」で使用するスライドのネタ検討その5です。

関数型言語における永続データ構造の運用イメージは「イミュータブルデータ構造は遅いような気がしていたが、別にそんなことはなかったぜ!」のキャッチが秀逸な「20分で分るPurely Functional Data Structures」がとてもいい資料です。

このような本格的な永続データ構造の実現はかなり難易度の高いプログラミングになりそうですが、有名所は専門家が開発したものがクラスライブラリに用意されているので、普通のプログラミングではそれを利用する技術を知っておけば十分です。

逆にアプリケーションのドメインオブジェクトを永続データ構造で実現する場合は、当然アプリケーション側で実装する必要があります。しかし、このような場合は難しいアルゴリズムは不要で、一定のイディオムを活用してサクサク作ってしまえばOKです。Scalaの場合は、性能が出なければ(難しいアルゴリズムは導入せず)そのままミュータブルにしてしまうという奥の手もあるので、最初の取っ掛かりは安全性重視でイミュータブルにしてしまうのがよいでしょう。

で、ドメインオブジェクトを永続データ構造で実現するときのアプローチの一つとしてケースクラスがあります。ケースクラスは「代数的データ型 on Scala」で説明した通り直積(Product)という側面もあり、sealed trait/abstract classと組合せて"直積の直和の総和"として代数的データ型を実現できます。

その一方で、シンプルな永続データ構造であればケースクラスで簡単に実現することができます。

人を表すPersonクラスを例にして実現方法を見ていきましょう。

定義

Personクラスを定義して、インスタンスを作成します。

scala> case class Person(name: String, age: Int)
defined class Person

scala> val taro = Person("Taro", 30)
taro: Person = Person(Taro,30)

永続データ構造として利用

永続データ構造に対する更新は、"「更新不可のオブジェクトを更新する」という矛盾を関数型プログラミングのテクニックで回避する"ことですが、基本的には更新部分の属性のみを変更した新しいオブジェクトを元のオブジェクトから複製することによって実現します。

基本となるアプローチはコンストラクタを使うもので、以下のようにコンストラクタに更新元のオブジェクトの属性値を並べて指定し、更新したい属性のみ新しい値を指定します。この場合はPersonの年齢を変更しています。

scala> val taro31 = Person(taro.name, 31)
taro31: Person = Person(Taro,31)
コピーコンストラクタ

コンストラクタですべてのパラメタを指定するのはプログラミングも大変ですし、デフォルトパラメタなどがある場合、パラメタ指定の抜けなどが発生してバグの元にもなります。このような問題を解決しているのがいわゆるコピーコンストラクタであるcopyメソッドです。

copyメソッドを使って、必要な属性のみを更新したオブジェクトを複製することができます。

scala> val taro31 = taro.copy(age = 31)
taro31: Person = Person(Taro,31)

永続データ構造的な機能追加

年齢の更新がPersonオブジェクトにとって、(1)使用頻度が高い、(2)ドメインモデル上重要な意味を持っている、(3)ドメイン特化のアルゴリズムを持っている、といった事情がある場合には、専用の更新メソッドを追加するのがよいアプローチです。

以下は年齢を加算するメソッドを追加したPersonです。こういった形で更新メソッドを定義し、実装にはcopyメソッドを使うのがイディオムです。

case class Person(name: String, age: Int) {
  def addAge(n: Int) = {
    copy(age = age + n)
  }
}

下のように使用します。

scala> taro.addAge(1)
res70: Person = Person(Taro,31)

ノート

以上で説明したようにケースクラスを用いると永続データ構造を簡単に作成することができます。ポイントとなるのはコピーコンストラクタで、変更対象となる属性のみを指定すると、その他の属性は自動的に設定された複製を作成してくれます。ケースクラスはこのコピーコンストラクタを自動的に定義してくれるのがとても重要な点です。

このように関数型プログラミングでの重要概念である代数的データ型と永続データ構造はどちらもScalaではケースクラスがキーとなる機能でした。

そういう意味で、代数的データ型/永続データ構造という枠組みの中でのケースクラスの活用がScalaプログラミングのコツということですね。

このあたりはScalaプログラミング的には非常に重要ですが、Monadicプログラミングからは距離があるので、セッション内でどの程度触れるのかは要検討です。

  • 代数的データ型&永続データ構造→ケースクラス

で一枚のスライドにまとめてしまうかもしれません。

2012年7月23日月曜日

MindmapModeling「顧客との絆づくり型O2Oで世界にも挑戦する無印良品」

7月21日(土)に横浜モデリング勉強会(facebook group)を行いました。また、会場には(株)アットウェア様の会議室をお借りしました。参加された皆さん、アットウェア様、どうもありがとうございました。

この勉強会で、浅海が作成したモデルを紹介します。モデルはMindmapModelingの手法で作成しました。(勉強会で使用したチュートリアル)

ワークショップの流れ

モデリング勉強会はワークショップ形式で以下の作業を行います。

  • 雑誌記事から情報システムの企画書、提案書、RFPの元ネタとなるモデルを作成する。

その上で、「要求仕様確認、実装可能性確認、開発のベースとなるプログラムを自動生成するモデルを目指」します。詳細は「ワークショップの進め方 (2012-06-16)」になります。

テーマ

モデリングの対象は、東洋経済誌の記事「顧客との絆づくり型O2Oで世界にも挑戦する無印良品」です。

後編を主の文章としましたが、前編もあわせて読んだ方がモデリングに必要な情報は収集しやすいようです。

O2Oは『MindmapModeling「KDDIがO2O事業を検討、無印良品やファミマが参加し実証実験」』でもテーマに取り上げました。クラウド時代のビジネスとITの接点として重要な分野になりそうです。

用語の収集と整理

まず用語の収集と整理します。

MindmapModelingに慣れてくると、用語がだいたいどこの枝に収まるのかわかるようになるので、用語を拾いながら、ラフなモデルを作っていきます。


今回の記事は、かなりざっくりした内容でO2Oの一般的な運用例という感じでした。そういう意味で「無印商品」が先駆者の一人ということだと思いますが、KDDIの記事と比べて新規性はあまり感じられず、O2Oだと大体こういう感じかな、という用語が採取されました。

物語

次の作業は「物語」です。

モデルは中心軸がないと単なる「用語」の集りなのでまとまりがでてきません。何らかの目的を実現するための構造を抽出したいわけですが、この「目的」と「目的を実現するための構造」を掬いとるためのツールとして有効なのが「物語」です。オブジェクト・モデリングの概念ではビジネス・ユースケースということになります。

「物語」を中心軸と定め、「物語」のスコープで用語を取捨選択、組織化し、足りない用語を補っていきます。

その手順は:

  1. 物語の名前をつける。目的(goal)が明確になる名前がよい。
  2. 物語の主人公、相手役、脇役などの登場人物を定める。
  3. 物語で使用する道具を定める。
  4. 出来事または脚本の列として脚本を記述する。

となります。2の登場人物と3の道具は最初から完全なものはできないので暫定的なものを定め、4の脚本の作業を通して洗練させていきます。


今回は、システムのインフラ部はO2Oでどのシステムも同じになりそうで、ここを精密にモデリングしても面白いものになりそうにありません。そこで、「無印良品」の物語をモデリングして、これをO2Oの観点で組織化してO2Oのインフラに合わせていくという方向性でモデリングを進めました。

物語を精密に組織化していこうとすると、現状のユースケース技術だと少し力不足かなという印象です。このあたりはメタモデルの機能拡張が必要に感じました。

最終型

前述の方針でさらに洗練を進めたモデルが以下になります。


時間切れでモデルはここまでとなりました。

「無印良品」の物語をシステム側で分析可能なデータとして蓄積していくメカニズムとして「道具」の「顧客時間」が使えそうなことが分かってきました。この「顧客時間」を会計システムの仕分け的なアプローチで、一つのイベントをシステムのいろいろな観点で同時にラベリングして、成分値も記録していくという実現方式です。どういう成分値のポートフォリオをどういうバランスで採取していくのかを時間をかけてじっくり検討していくと面白そうです。

O2O

『MindmapModeling「KDDIがO2O事業を検討、無印良品やファミマが参加し実証実験」』に続いてO2Oは2回目ですが、インフラ部分の要素はほぼ共通で、パッケージなりクラウドサービスなりで実現することが可能な技術という印象です。そうなると、モデリングについてはインフラ部分の設計はあまりニーズがなく、ビジネスそのものをITシステムに接続するためのモデルが重要になりそうです。いわゆるERPのフィットギャップ分析のようなものですね。

さらに考えていくと、上流のモデルからプログラムの自動生成でO2Oフレームワーク向けのコードが生成されれば理想的です。

完全な自動生成は無理としても、ビジネス・モデルを記述したものから、特定のスコープは自動生成、それ以外はスクラッチで開発といった開発の流れが見えてきます。

クラウド時代に入って、色々な技術が新たに登場してきますが、業務アプリのインフラ側は新技術の登場から一定期間後にパッケージやクラウドサービスで提供されることを考えると、業務アプリ側の立場としては個々のインフラ技術を細かく追いかけていくのは効率的なアプローチではないかもしれません。それより、クラウドプラットフォームの特性をざっくりと把握して、これを業務とつなげていくモデリング技術の重要性がより高まるのではないかと感じました。

次回

8月は一回お休みして次回は9月中旬(第3週土が候補)です。

今回と同じく「ワークショップの進め方 (2012-06-16)」の手順で、「雑誌記事から情報システムの企画書、提案書、RFPの元ネタとなるモデルを作成する」を行う予定です。