ラベル cloud の投稿を表示しています。 すべての投稿を表示
ラベル cloud の投稿を表示しています。 すべての投稿を表示

2016年8月31日水曜日

クラウド・アプリケーション・モデリング

製品開発を通じて色々と経験を積むことができたので、一度腰を落としてクラウドアプリケーション開発向けの開発手法について考えてみることにしました。何回かに分けて考察していきたいと思います。

考察する開発手法は以下の2つの機能で構成されています。

  • 開発プロセス
  • モデル体系(メタモデル)

今回は開発プロセスの全体像についてまとめます。

クラウドアプリケーションは、スクラッチで開発するケースもあると思いますが、主流はクラウドアプリケーション向けのプラットフォーム上で必要な機能のみを開発することになるのではないかと推測しています。

この場合、クラウドアプリケーションの開発手法はクラウドアプリケーションのプラットフォーム上での開発を前提としたものが重要になってきます。

本稿ではこの前提の上で、クラウドアプリケーションのプラットフォーム上でのクラウドアプリケーション開発向けの開発手法をテーマにします。

アプリケーション開発の基盤となるクラウドプラットフォームをApplication Cloud Platform(以下ACP)と呼ぶことにします。

背景

クラウドアプリケーションは以下のような既存のアプリケーションの複合体と考えることができます。

  • a) 消費者向けUX指向のBtoCアプリケーション
  • b) 基幹システムや外部システムとの連携を行うInB, BtoB的な業務アプリケーション
  • c) 利用者同士を結びつけてコミュニティを構築するPtoPアプリケーション
  • d) 統合運用・管理システム
  • e) 蓄積されたデータに対する分析・評価基盤

このような複雑なシステムをスクラッチで開発することはコストもかかりますし、長期の開発期間を必要とするためビジネス的にタイムリーなタイミングでリリースすることが難しくなります。

ACPは上記のような機能をビルトインしたプラットフォームであり、UXを実現するWeb/モバイルアプリと必要最小限のサーバー機能を開発するだけで、アプリケーションを開発することができます。

このようにACP上で動作するアプリケーション開発を行う場合、スクラッチでアプリケーション全体を構築する場合とは、異なった開発手法になってきます。

また、クラウドアプリケーションではアプリケーション開発の力点が、「アプリケーションを開発」することから、「ビジネスを駆動」することに移っていることも見逃すことができません。 

単にアプリケーションを構築するだけでは不十分で、ビジネスのライフサイクルの中でアプリケーションの開発、運用、評価から次のビジネス設計に繋がるサイクルをまわしていく仕掛けを構築し、ビジネスに組込んでいくことが求められます。

以上のような点を念頭に置いたアプリケーションの開発手法が必要になってきます。これが本稿のテーマです。

開発プロセスの全体像

開発プロセスは4つのアクションで構成されます。

  • Business Modeling :: ビジネス設計を行います。
  • Development :: ACPアプリケーションの開発を行います。
  • Operation :: アプリケーションを配備して運用します。
  • Evaluation :: アプリケーションの動作結果を分析しビジネスや開発にフィードバックします。

これらの4つのアクションで一種のPDCAサイクルを構成します。



Business Modelingは、ビジネスレイヤーでの目標、価値などの分析を行いビジネスプロセスなどを用いてビジネス設計を行います。このビジネス設計の中で必要とされるアプリケーション・プログラムの責務が明らかになるので、これをDevelopmentアクションで開発を行います。

Developmentアクションでは、Business Modelingの成果物をベースにアプリケーションの開発を行います。Developmentアクションは必要に応じて繰り返し開発を行います。

クラウドアプリケーションにおいて重要な点として、アプリケーションの運用によって得られた各種データを分析してビジネスに対するフィードバックするというビジネス改善サイクルをどのようにして構築してビジネスに組込んでいくのかという点があります。

伝統的なアプリケーション開発ではこの活動はあまりフォーカスされていなかったので開発プロセスとしては中心的な話題にはなってこなかったと思います。(そもそもプログラムを作るのが大変だったので、そちらへの対応で力を使いきってしまっていたといえるかもしれません。)

一方、クラウドアプリケーションの場合ビジネス改善サイクルの構築とビジネスへの組み込みをいかに短く効率よく行うのかという点が主要な論点の一つとなっていると思います。1つにはACPによって複雑なバックエンドシステムを持つプログラムを開発することが簡単になったこと。また、ビッグデータ的なデータ活用技術の実用化によってビジネスにフィードバックするデータの分析が安価かつ容易にできるようになりました。

Operationで分析に必要なデータを蓄積すること、Evaluationで蓄積したデータを効率よく解析・分析してビジネスやアプリケーションの改善に必要なフィードバックを作成する作業をいかに行うのかという点が重要になります。

これらの2つのアクションではACPがプラットフォームとして提供するデータ分析基盤が非常に重要になります。

ACPのデータ分析基盤では、Operationアクションで各種データを標準形式で採取し、分析に適した形の分析データに再構成して管理します。Evaluationアクションではこれらの分析データを元に、各種分析を行いビジネスやアプリケーションへのフィードバックを作成します。

また、Business Modelingでは単にアプリケーションの要求仕様を抽出するだけでなく、全体ビジネスの中でのアプリケーションの位置付け、ビジネスの目標、価値といったものを踏まえてビジネス改善サイクルを組み込んだビジネス設計も同時に行うことになるでしょう。

開発の流れ

Developmentアクションでの開発の流れは以下になります。

  • Requirement
  • Design
  • Implementation
  • Test
  • Integration Test

開発の各アクティビティは標準的なものです。

ただし、ACPを使う場合ターゲットのプラットフォームは決まっているためPSM(Platform Indepent Model)を作成するAnalysisは効果が薄いので省略しています。必要に応じてDesignの中で行うとよいでしょう

これらのアクティビティは流れ作業的に順番にこなしていくのではなく、適切なプロジェクト運営のもと効率のよい順序で作業していくことになります。

目安としては、図で示した通りDevelopmentアクション全体のイテレーション、Design, Implementation, Testのイテレーションを想定しています。

サブシステム毎の開発手順カスタマイズ

図はDevelopmentアクションのRealization部を詳細化したものです。



ここでは複数のサブシステムを個別に開発し、最終的に結合する開発を想定しています。

図では特に重要なものとしてPresentation, Service Extension, Configurationを示しています。

Presentation

PresentationはWebやiOS/AndroidといったMobileアプリの開発です。このRealizationは以下の3つのactivityで構成されることを想定しています。

  • UX Design :: 画面設計を行います。
  • Implementation :: アプリケーションを実装します。
  • Test :: アプリケーションのテストを行います。

UX Designは画面設計や画面とAPCの提供するAPIなどの接続関係を設計します。

UX Designで行う画面設計はRequirementとの境界線が曖昧になりますが、開発中に顧客の意見を取り入れながら画面の調整を行う作業はUX Designの一環とします。(逆にRequirementでは、ペルソナやユースケースで利用者の体験する物語とドメインモデルの定義といった抽象度の高い成果物が中心となります。)

プロジェクト運営はアジャイル的なものを想定しています。

UX Design, Implementation, Testはこの順番に行うのではなく、作業の都合に合わせて任意の順番で行っていきます。

Service Extension

Service Extensionは、ACPが提供する拡張メカニズムに則ったプラグイン的な機能です。

ACP内に配備することで、ACPに対してアプリケーションが必要とする機能拡張を行います。

プロジェクト運営は計画駆動型を想定しています。

Service Extensionでは、基幹システムとの接続など、仕様がrequirement activityで明確に定義されるケースが多いと考えられるので計画駆動型が適していると考えています。もちろん、小規模開発など、場合によってはアジャイル型のプロジェクト運営が適しているケースも多いでしょう。

Configuration

アプリケーション向けのACPの振る舞いをカスタマイズパラメータとして設定します。

ここでは意図を明確にするために、独立した開発として示していますが、小規模開発の場合はPresentationなどの作業内で行われることになると思います。

Web/Mobileアプリ開発プロセス

「概要」の図に示す開発プロセスは汎用的な大きめの開発手順になっています。

さらに「サブシステム毎の開発手順カスタマイズ」の図ではPresentation, Service Extension, Configurationといった開発ターゲットごとの開発手順としてカスタマイズしています。



クラウドアプリケーションで多い形態は、フロントエンドのWeb/MobileアプリケーションのみUX重視で開発し、バックエンドサービスはACPのものをそのまま利用するものです。

このような開発では、多少ACPに対するconfigurationが発生しますが、基本的には従来技術であるWeb/Mobileアプリケーションの開発手法をそのまま用いてクラウドアプリケーションを開発できます。

Web/Mobileアプリ・プロファイルではこのような開発を想定したカスタマイズを行っています。

OperationやEvaluationはACPが自動的に提供してくれるため、開発エンジニアが特別に何かを開発する必要はありません。ただ、ACPが提供するAPIを使用する手順などで、何らかの意識は必要なのでWeb/Mobile開発者向けのユーザーガイドなどで、この点は明確化しておくことになるでしょう。

まとめ

今回はACPをベースにした、クラウドアプリケーションの開発プロセスの全体像についてざっくり考えてみました。

面白いテーマなので、引き続き考えていることをブログ化していきたいと思います。

2012年8月21日火曜日

クラウド温泉3.0 / Monadicプログラミング・マニアックス Index

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」関連記事のIndexです。

クラウド温泉

クラウド温泉全体の感想は以下になります。

スライド

Monadicプログラミング・マニアックスのスライドです。

記事一覧

「Monadicプログラミング・マニアックス」のスライドの作成過程のメモ記事の一覧です。

ノート

"関数型プログラミングはパイプライン・プログラミング"、と考えてみるとイメージも分かりやすいし、実際のプログラミング時のガイドとしても有効なので、その観点でまとめてみよう、というのが今回のセッションの趣旨です。

元々Haskell的、Scalaz的Monadicプログラミングをやろうと思ったのは、ScalazのPromiseを見て衝撃を受けたのがきっかけなので、PromiseとKleisliやApplicative Functorを組合せて並列プログラミングした効果を実測値付きで盛り込めて目標の一つは達成できたかなと思います。

実際に書き始めてみると、Monadicの話に行く前に今風の関数型プログラミングの基礎といったものが必要なことが分かったので、その部分を厚くしてみました。逆に、当初予定していたReducerを使ってMonoidを任意のドメインオブジェクトに接続する手法や、TraverseでKleisliをオブジェクトの集まりに適用する話題(これはミニセッションのテーマにもなった「Iteratorパターンの本質」とも関係してきます)、Validationを使ったMonadicプログラミングの具体例、といった話題は見送りました。

ボク自身もScalazを本格的に使い始めたのはここ一年ぐらいなので、まだまだ山の麓の所にいるわけですが、色々と追いかけていく方向性も見えてきたので、来年のクラウド温泉でもさらにマニアックな話題で何か発表できればと思っています。

2012年8月20日月曜日

クラウド温泉3.0

土日にクラウド温泉3.0@小樽に参加していました。今年のクラウド温泉もエキサイティングな内容で、とても楽しめました。幹事の中村さん(@nakayosix)、参加者の皆さん、どうもありがとうございました。

Monadicプログラミング・マニアックス

ボクは初日の最初の枠と2日目のフリーディスカッションの枠を担当しました。最初の枠で「Monadicプログラミング・マニアックス」というテーマでお話させていただきました。

内容はブログに書いてきたことをまとめたものになっています。PromiseモナドやApplicative Functorを使った並列プログラミングのコード例は参考になるかもしれません。

業務システムで使う型クラス

Scalaでは型クラスが使えるわけですが、オブジェクト指向のクラスインヘリタンスとかぶる機能でもあり使い方が難しい言語機能です。

神崎さんのこのセッションは、業務システムで型クラスを使用するトライアルについての報告で、とても参考になりました。

私見では、(Scalaの)型クラスの旨みは(1)コンパイル時に静的に多相性が解決されるためコンパイル時にエラー検出できる、(2)関数の選択が、主体、客体、文脈の組合せで行われる、(3)お互いに疎に開発した主体、客体をソースコードを変更することなく、型クラスインスタンスの追加(必要に応じてのみで接続することができる、といったところにあると考えています。

主体、客体を疎結合にできるため、部品の再利用性が高まることが期待できます。

ただし、(a)使うための決め事がちょっとある、(b)プラグインのような形でプログラムのバイナリを後付けで追加することはできない、という弱点もあります。

Scalaはオブジェクト指向言語なので、普通にちょっとした多相性が必要な時は、クラスインヘリタンスを使う方が圧倒的に楽です。逆に型クラスは気軽に定義して使うというような手軽さはないので、何か明確なメリットがある場合でないと採用しづらい技術です。

そういった点も含めてクラスと型クラスの使い分けというのを考える良いきっかけになりました。

ぶいてく流スケーラブルアプリの作り方 2012

時間があればKVSにBarkley DBを採用した理由、KVS特有のアプリケーション開発のデザインパターン(シャーディングのためのID管理、トランザクション境界の切り方、バッチ処理の組み方など)を議論したいところでした。かなり濃いメンバーが集まっていたので、製品紹介はパンフレット見てね、で終了して、冒頭から上記のようなテーマで議論に入るとより面白かったかもしれません。

LTタイム

LTタイムでは以下の発表が行われました。

  • 中村 良幸 (@nakayoshix) 「クラウド禅問答」
  • 鈴木 常彦 (@tss_ontap) 「IPv6の理想と現実〜若者に未来を託して〜」
  • @sumim「EclipseとPharo SmalltalkのTDD対決ムービーを副音声で解説!」
  • 小田 朋宏 (@tomooda) 「HaskellとSmalltalkの会話」「画面ゆらゆら系デモ」
  • 神林 飛志 (@okachimachiorz) 「Asakusa Framework 近況報告」

中村さんの「クラウド禅問答」はオフレコwなのでここでは触れないこととして、鈴木さんの「IPv6の理想と現実〜若者に未来を託して〜」はIPv6の現状がよくわかってとても参考になりました。IPv6といえば、1990頃にボクがメーカーのネットワーク部隊に居た頃、隣に居た人が担当していて色々やっていたのを思い出します。もう20年経ちますが、相当の前進はあったものの、未だに問題含みで実用には距離がありそうな感触でした。すでに稼働しているエコシステムを丸ごと取り替えるような新技術の普及はなかなか難しいですね。

Smalltalkはボクにとっては未知の分野ということもあって、@sumimさんと小田さんのセッションは興味深く聞けました。

プログラマのための代数入門2 Monadへの道

二日目の最初のコマは中村さんの代数入門「Monadへの道」です。代数の基本からのアプローチだったので、時間的に厳しかったかもしれません。ボク的にはScalaと圏論のマッピングのあたりから始めてもらえるとうれしかったわけですが、関数型をやっていない参加者のことを考えるとそうはいかないところですね。今回の収穫はWikipediaベースで調べていくのはあまりよくないということでした。

最後は定番の資料をベースに解説が行われました。(資料はリンクが分かり次第追加します。)

基礎から見直すTX処理〜A Critique of ANSI SQL Isolation Levelsを再読する

今回抜群に面白かったのが神林さんのTX解説です。

A Critique of ANSI SQL Isolation Levels」はDBの分野では超有名な論文とのことで、この論文を分かりやすく解説していただきました。「Transactional Information Systems」では第3章「Concurrency Control: Notions of Correctness for the Page Model」が相当するようです。

このTX解説を聞けただけで、クラウド温泉に参加した元は取れたという感じです。

本当は怖いDNSの話

鈴木さんの本当に怖い話。インフラのど真ん中のホラーなので、見なかったことにしておきたいwところです。

クラウドアプリを作る上では、こういったセキリティ技術も押さえておきたいので、技術的にもとても参考になりました。

Object Functionalをテーマとしたディスカッション

二日目の午後は、小樽商大の教室をお借りしてObject-Functionalについてフリーディスカッションしました。

ボクがネタとして出した資料は以下です。

やはりモナド周りが議論の中心になりました。

スライド中「関数型言語の技術マップ」で二点ほど修正案を頂いたので、これは持ち帰って考えてみたいと思います。

純粋関数型言語が今まであまり普及せず最近使われるようになってきた理由についてですが、最近の関数型言語はモナドの登場で状態(やIO)が記述できるようになったことが大きいのではないかという意見がありました。確かに、これは大きな理由かもしれません。

ディスカッションの続きとして急遽以下のミニセッションも行われました。

  • 「Iteratorパターンの本質」読書会
  • Haskelシューティングゲーム解説

『「Iteratorパターンの本質」読書会』は初日にボクが挙げた参考資料である「Iteratorパターンの本質」をスライドで見ながらディスカッションをしていくというものです。

この資料はMonadicプログラミングの重要な要素技術が網羅されているのでディスカッションの元ネタとしてとてもよい感じだったと思います。

「Haskellシューティングゲーム解説」は作者の@c255さんにHashkellで記述したシューティングゲームのソースコードを直接解説してもらうという贅沢なセッションでした。シューティングゲームが純粋関数型言語で作れるというのは驚きです。

Haskellを使うことで、プログラムの構造を大幅に変更しても、問題をコンパイラがエラーで弾いてくれるのでバグが出にくいということでした。

Scalaでも事情は同じで、リファクタリングした時の開発効率とバグの出にくさが静的型付けの大きなメリットだと思います。

まとめ

今回のクラウド温泉は、「代数スペシャル&Object Functionalとは」がテーマでした。

関数型言語もHaskell的なモナドベースのものは、圏論や群論といった代数的な操作がプログラミングの軸になります。Scala&Scalazも事情は同じですね。

また、クラウドアプリケーションで更に存在感が増すトランザクション技術においても、「基礎から見直すTX処理〜A Critique of ANSI SQL Isolation Levelsを再読する」にあったように代数的な操作による最適化が重要ということがわかりました。

クラウド時代のアプリケーション開発では、どうも代数が重要な要素技術、基盤技術の一つということになりそうというのが、今回の温泉のメッセージだったと総括できるように思います。

その上で、既存のオブジェクトの世界とどのようにつなげていくのかという議論に進んでいくわけですね。

今回も濃いメンバーの人たちと、クラウド、代数、オブジェクトについて色々と議論ができてとても楽しかったです。来年のクラウド温泉にもぜひ参加したいと思います。

2012年8月10日金曜日

クラウド温泉3.0 (18) / Promise

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

関数型プログラミングが期待されている大きな理由の一つは並列プログラミングです。今後プロセッサのコア数が急激に増えるのは確実で、複数コアを活用して高速に動作するプログラムを空気のように作ることが必須になります。

また、メモリ容量の増大やSSDの採用でデータベースもインメモリ化されてくると、従前のようなI/Oのボトルネックが解消してきます。このためアプリケーション側の並列処理にかかる比重が高まってくることになります。

このため、並列プログラミングは気合を入れて行う特別なものではなく、毎日のように接するコモディティとなるでしょう。普通に書くものが普通に並列動作するようになっていなくてはならないわけです。しかし、Javaなどの既存のオブジェクト指向言語では、並列プログラミングの難易度は高く、並列プログラミングのコモディティ化はとても達成できそうにありません。

関数型プログラミングの場合も並列プログラミングには色々なアプローチがありますが、有力なアプローチとなるのがモナドによる並列処理の隠蔽です。通常のMonadicプログラミングをしておくと、モナドの裏側で自動的に並列処理をしてくれます。この方法であれば、日常的に並列プログラミングしても、プログラマにかかる負担は多くありません。(そのかわりMonadicプログラミングをマスターするというハードルが追加されます。)

ScalazのPromiseは並列処理を隠蔽したモナドです。今回はPromiseによる並列プログラミングについて見ていきます。

準備

準備として動作時間を取得するための関数goを定義します。

def go[T](a: => T): (T, Long) = {
  val start = System.currentTimeMillis
  val r = a
  val end = System.currentTimeMillis
  (r, end - start)
}

テストに使用する関数fは引数に取ったInt値を100msウエイト後にそのまま返します。

val f = (x: Int) => {
  Thread.sleep(x * 100)
  x
}

f関数をgo関数上で動作させると以下になります。10×100msで一秒後に値10が返ってきています。

scala> go(f(10))
res176: (Int, Long) = (10,1000)

PromiseのKleisli

Monad活用のコツの一つはクライスリ関数です。Int値を取りMonadであるPromiseを返すクライスリ関数をKleisliでくるんだものを定義しておきます。

scala> val fp = f.promise
fp: scalaz.Kleisli[scalaz.concurrent.Promise,Int,Int] = scalaz.Kleislis$$anon$1@9edaab8

Applicative

まず普通にf(1)、f(2)、f(3)を順に足していく処理です。先頭から順に関数の実行時間も足し込まれるので600msかかります。

scala> go(f(1) + f(2) + f(3))
res212: (Int, Long) = (6,603)

PromiseのKleisliとApplicativeを使って同様の処理を行うと300msで実行できました。これは、fp(1)、fp(2)、fp(3)の処理が平行実行されたため、最も長いfp(3)の300msが全体の実行時間になったためです。

scala> go((fp(1) |@| fp(2) |@| fp(3))(_ + _ + _).get)
res215: (Int, Long) = (6,302)

ここで重要な点は、平行処理に対する同期処理はまったく記述していない点です。普通のMonadicプログラミングを行うだけで、Promiseモナドが自動的に同期処理を行なってくれます。

上記が普通のMonadicプログラミングというのは、以下のOptionに対する処理と見比べてみるとよくわかります。

scala> go((1.some |@| 2.some |@| 3.some)(_ + _ + _).get)
res237: (Int, Long) = (6,1)

つまり普通のMonadicプログラミングをすれば自動的に並列プログラミングになるわけです。Monadicプログラミングのハードルは高いものの、このハードルだけクリアしておけば自動的に並列プログラミングがついてきます。Javaで並列プログラミングを学習するコストと、実際のプログラミング時の手間やデバッグ効率を考えると、Monadicプログラミングのハードルの方がはるかに低いといえます。

Listに対する並列プログラミング

Listに対して普通に関数fを適用すると、関数fの実行時間が全て足し込まれたものが全体の処理時間になります。以下では1.2秒かかっています。

scala> go(List(1, 2, 3).map(f).map(f))
res221: (List[Int], Long) = (List(1, 2, 3),1205)

Promise(+Kleisli)を使うと以下のように600msで処理が完了しました。一段目のmap処理の300msと二段目のmap処理(内部でflatMap)の300msを足して600msになります。

scala> go(List(1, 2, 3).map(fp).map(_.flatMap(fp)).sequence.get)
res220: (List[Int], Long) = (List(1, 2, 3),602)

Kleisli関数の合成

Kleisliの威力を見るために、もう少し処理を複雑にしてみます。

関数fと連続実行した時に処理の合計が400msになる関数fxを定義します。同時にこれをPromiseのKleisli化したものも定義します。

val fx = (x: Int) => {
  val t = math.abs(x - 4)
  Thread.sleep(t * 100)
  x
}

val fxp = fx.promise

関数fと関数fxをListに対して普通にmapで連続実行した場合ですが、以下のように1.2秒かかりました。

scala> go(List(1, 2, 3).map(f).map(fx))
res223: (List[Int], Long) = (List(1, 2, 3),1205)

関数fと関数fxを合成した場合も1.2秒は変わりません。

scala> go(List(1, 2, 3).map(f >>> fx))
res232: (List[Int], Long) = (List(1, 2, 3),1205)

関数fと関数fxをPromiseのKleisli化した関数fpと関数fxpを連続実行したものが以下になります。内部で並列処理が行われ400msで実行が完了しました。

scala> go(List(1, 2, 3).map(fp).map(_.flatMap(fxp)).sequence.get)
res222: (List[Int], Long) = (List(1, 2, 3),402)

また、Klisliのfpとfxpを合成したものをmapで適用しても同様に400msで実行が完了しました。

scala> go(List(1, 2, 3).map(fp >=> fxp).sequence.get)
res230: (List[Int], Long) = (List(1, 2, 3),402)

PromiseのKleisliでは、Kleisliをパイプラインの各段に適用しても、Kleisliを合成したものを一度に適用しても、同じように並列処理が行われました。どちらのアプローチでも同じように並列処理が行われるというのは、プログラミングのしやすさからは非常に重要な性質です。

バージョン

セッションで用いるScalaとScalazはそれぞれ2.9.2と6.0.4です。

ScalaとScalazとも新バージョン(Scala: 2.10, Scalaz: 7)がリリースされる直前になっています。Scala 2.10では並列プログラミング周りが大きく変更されます。Scalaz 7もこの影響を受けるものと考えられます。

このため、本セッションで扱う内容はすぐに古くなってしまうことが確実ですが、基本的な考え方は引き続き有効だと思います。

2012年8月9日木曜日

クラウド温泉3.0 (17) / SQL

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

「Monadicプログラミング・マニアックス」のネタを要素技術のボトムアップで積み上げてきましたが、なかなか本丸に到達しないままクラウド温泉も来週末に迫って来ました。

来週はお盆休みでブログはお休みする予定なので、今日と明日の二回は応用からのトップダウンでネタを整理したいと思います。

SQLと関数型言語

関数型プログラミンをやっていて感じるのは、SQLなどの問い合わせ言語と関数型プログラミングの相性がとてもよいことです。現状はScalaとSQLが直接接続できるというわけではありませんが、データフローの実現で関数型プログラムとSQLを組合せる場合、同じセマンティクスの土俵の上で一気通貫に処理を考えることができます。これは一般的に問い合わせ言語が一種の関数型言語であるのが理由だと思います。その上で、プラットフォーム上の制約を考えて実装をプログラムとSQLとに振り分けるという形になります。

このアーキテクチャ上ではORMはちょっと中途半端な感じで、問題を複雑化させる要因になるかと思います。

現状のORMの問題の一つは、実現がER図的な共通ドメイン・モデル側に寄りすぎていて、ユースケース・スコープのドメイン・モデルはうまくハンドリングできない点です。この問題も関数型プログラミング&SQLでうまくさばくことができます。

またORMの実現方式にもよりますが以下のような問題もあります。

  • 性能チューニングや処理の記述でSQLの力を十分に活かせない。
  • データベーススキーマの変更や業務の変更に弱い。

こういう問題があるので、今までもプロ筋はiBatis/MyBatisを好んで使う傾向があったと思います。

問い合わせ言語はSQLだけではなく、各種のNoSQLにもそれぞれの問い合わせ言語がありますし、XMLではXQueryだけでなくXSLT、XPathも一種の問い合わせ言語と考えることができます。いずれも関数型言語としてとらえなおしてみると、関数型プログラミングとの併用で面白いユースケースが見つかりそうです。

SQLと関数型言語の接続

問い合わせ言語としてのSQLの特徴の一つは、問い合わせ結果が表形式になるということです。問い合わせの結果得られる情報は色々な構造を持つわけですが、一つの表にそういった構造がエンコードされて入ってきます。これをデコードするのが、プログラム側の最初の処理になります。

エンコードされる情報は木構造だったり、グラフ構造だったりするわけですが、これをデコードするコーディングはイディオム化できます。このイディオムを覚えておけば、SQLと関数型プログラミングを自由自在に接続することができるわけです。

組織階層図

例題としてRDBMSの上の部門マスタから組織ツリーを生成するプログラムを考えてみましょう。以下のようなSQLで部門マスタから部門情報を取り出すとします。

select C.部門ID as 部門ID, C.部門名 as 部門名,
       P.部門ID as 親部門ID, P.部門名 as 親部門名
  from 部門マスタ as C
    left outer join 部門マスタ as P on C.親部門ID=P.部門ID

プログラム

プログラムはSQLのアクセスは省略して、SQLの結果がMapのListとして返ってきた所から始めることにします。PlayのAnormがこういったアクセス法の代表例です。

変数recordsにデータが格納されています。

package tryout

import scalaz._, Scalaz._

object SqlSample {
  val records = List(Map('部門ID -> 1,
                         '部門名 -> "営業統括",
                         '親部門ID -> None,
                         '親部門名 -> None),
                     Map('部門ID -> 11,
                         '部門名 -> "東日本統括",
                         '親部門ID -> Some(1),
                         '親部門名 -> "営業統括"),
                     Map('部門ID -> 12,
                         '部門名 -> "西日本統括",
                         '親部門ID -> Some(1),
                         '親部門名 -> "営業統括"),
                     Map('部門ID -> 13,
                         '部門名 -> "首都圏統括",
                         '親部門ID -> Some(1),
                         '親部門名 -> "営業統括"),
                     Map('部門ID -> 111,
                         '部門名 -> "北海道支店",
                         '親部門ID -> Some(11),
                         '親部門名 -> "東日本統括"),
                     Map('部門ID -> 112,
                         '部門名 -> "東北支店",
                         '親部門ID -> Some(11),
                         '親部門名 -> "東日本統括"),
                     Map('部門ID -> 113,
                         '部門名 -> "北陸支店",
                         '親部門ID -> Some(11),
                         '親部門名 -> "東日本統括"),
                     Map('部門ID -> 114,
                         '部門名 -> "中部支店",
                         '親部門ID -> Some(11),
                         '親部門名 -> "東日本統括"),
                     Map('部門ID -> 121,
                         '部門名 -> "近畿支店",
                         '親部門ID -> Some(12),
                         '親部門名 -> "西日本統括"),
                     Map('部門ID -> 122,
                         '部門名 -> "中国支店",
                         '親部門ID -> Some(12),
                         '親部門名 -> "西日本統括"),
                     Map('部門ID -> 123,
                         '部門名 -> "四国支店",
                         '親部門ID -> Some(12),
                         '親部門名 -> "西日本統括"),
                     Map('部門ID -> 124,
                         '部門名 -> "九州支店",
                         '親部門ID -> Some(12),
                         '親部門名 -> "西日本統括"),
                     Map('部門ID -> 125,
                         '部門名 -> "沖縄支店",
                         '親部門ID -> Some(12),
                         '親部門名 -> "西日本統括"),
                     Map('部門ID -> 131,
                         '部門名 -> "東京支店",
                         '親部門ID -> Some(13),
                         '親部門名 -> "首都圏統括"),
                     Map('部門ID -> 132,
                         '部門名 -> "北関東支店",
                         '親部門ID -> Some(13),
                         '親部門名 -> "首都圏統括"),
                     Map('部門ID -> 133,
                         '部門名 -> "南関東支店",
                         '親部門ID -> Some(13),
                         '親部門名 -> "首都圏統括"))

  def main(args: Array[String]) {
    val a = build部門(records)
    val b = buildTree(a)
    showTree(b)
  }

  def build部門(records: Seq[Map[Symbol, Any]]): Map[Int, 部門] = {
    records.foldRight(Map[Int, 部門]())((x, a) => {
      val 部門ID = x('部門ID).asInstanceOf[Int]
      val 部門名 = x('部門名).asInstanceOf[String]
      val 親部門ID = x('親部門ID).asInstanceOf[Option[Int]]
      val 親部門名 = x.get('親部門名).asInstanceOf[Option[String]]
      a + (部門ID -> 部門(部門ID, 部門名, 親部門ID, 親部門名))
    })
  }

  def buildTree(sections: Map[Int, 部門]): Tree[部門] = {
    def build(sec: 部門): Tree[部門] = {
      val children = sections collect {
        case (k, v) if v.親部門ID == sec.部門ID.some => v
      }
      node(sec, children.toStream.sortBy(_.部門ID).map(build))
    }

    build(sections(1))
  }

  def showTree(tree: Tree[部門]) {
    println(tree.drawTree(showA[部門]))
  }
}

case class 部門(部門ID: Int, 部門名: String,
                親部門ID: Option[Int], 親部門名: Option[String])
ドメイン・モデル

検索結果をアプリケーションのドメイン・モデルに写像するわけですが、ドメイン・オブジェクトとして「部門」を定義しました。

業務アプリケーションの場合、ドメイン・モデルとのインピーダンス・ミスマッチを極力排除するためにドメイン・モデルが日本語の場合はプログラムの識別子にも同じ日本語を使うべき、と考えているので業務アプリケーションらしくこのサンプルもそうしています。DDDにおけるユビキタス言語の実践の一つですね。

build部門メソッドはrecordsの値(SQLでの検索結果)から部門一覧のMapを作成します。このメソッドが、SQLの結果とドメイン・モデルを写像する前半分の処理になります。

build部門メソッドでは、関数型プログラミングでおなじみのfoldRightメソッドを使って、問い合わせ結果を部門のMapに畳み込んでいます。Mapに対する畳込みがこの際のイディオムですね。

木構造の実現

buildTreeメソッドは部門一覧の表から部門の木構造を生成します。このメソッドが、SQLの結果とドメイン・モデルを写像する後半分の処理になります。

部門ツリーはScalazの代表的な永続データ構造であるTreeを用いて木構造を構築しています。

木構造の表示

部門の木構造の表示はshowTreeメソッドで行なっています。ScalazのTreeの機能を用いるので簡単に表示を行うことができます。

実行

プログラムを実行すると、以下のように組織ツリーが表示されました。

部門(1,営業統括,None,Some(None))
|
+- 部門(11,東日本統括,Some(1),Some(営業統括))
|  |
|  +- 部門(111,北海道支店,Some(11),Some(東日本統括))
|  |
|  +- 部門(112,東北支店,Some(11),Some(東日本統括))
|  |
|  +- 部門(113,北陸支店,Some(11),Some(東日本統括))
|  |
|  `- 部門(114,中部支店,Some(11),Some(東日本統括))
|
+- 部門(12,西日本統括,Some(1),Some(営業統括))
|  |
|  +- 部門(121,近畿支店,Some(12),Some(西日本統括))
|  |
|  +- 部門(122,中国支店,Some(12),Some(西日本統括))
|  |
|  +- 部門(123,四国支店,Some(12),Some(西日本統括))
|  |
|  +- 部門(124,九州支店,Some(12),Some(西日本統括))
|  |
|  `- 部門(125,沖縄支店,Some(12),Some(西日本統括))
|
`- 部門(13,首都圏統括,Some(1),Some(営業統括))
   |
   +- 部門(131,東京支店,Some(13),Some(首都圏統括))
   |
   +- 部門(132,北関東支店,Some(13),Some(首都圏統括))
   |
   `- 部門(133,南関東支店,Some(13),Some(首都圏統括))

2012年8月8日水曜日

クラウド温泉3.0 (16) / Kleisli

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

関数型プログラミングでは、引数が1つの関数が非常に重要です。極端に言うと引数が1つの関数を合成して、プログラムを構築していきます。

もちろん引数が2以上の関数も使いますが、この場合はカリー化や部分適用を使って引数が1つの関数に持っていくのが重要なテクニックになっています。

引数の数が1の関数

引数の数が1つの関数として、3倍する関数mul3と5を加える関数plus5を定義します。

val mul3 = (_: Int) * 3
val plus5 = (_: Int) + 5

引数1の関数は以下のようにmapメソッドといった高階関数に指定して処理を合成することができます。多くの高階関数は引数の数が1の関数を入力にするので、引数の数が1の関数は再利用可能な部品として利用価値が高くなります。

scala> List(1, 2, 3).map(mul3)
res19: List[Int] = List(3, 6, 9)

A→M[B]

FunctorのmapメソッドはA→Bという形の関数を引数に取りますが、MonadのflatMapはA→M[B]という形の関数を引数に取ります。MはMonadなので、AからBをMonadでくるんだものを返す関数ということになります。A→M[B]も引数の数が1の関数の一種ですが、Monadを返すところが特殊化されています。

Monadは関数型プログラミングに非常に重要な構成要素です。つまり、A→M[B]の関数も再利用可能な部品として利用価値が高いと考えられます。

先ほど定義したmul3とplusをMをListとしてA→M[B]化した関数mul3lとplus5lは以下になります。狙いが分かりやすいようにmul3lは0以上の値、plus5lは偶数を有効として扱うように機能追加しました。

val mul3l = (x: Int) => if (x >= 0) List(x * 3) else Nil
val plus5l = (x: Int) => if (x % 2 == 0) List(x + 5) else Nil

mul3lをListのmapメソッドに適用すると以下のようになります。

scala> List(1, -2, 3, 4).map(mul3l)
res33: List[List[Int]] = List(List(3), List(), List(9), List(12))

さて、mul3lをListのflatMapメソッドに適用すると以下になります。-2に対応する要素が結果から取り除かれていますが、これがモナドの効果です。

scala> List(1, -2, 3, 4).flatMap(mul3l)
res34: List[Int] = List(3, 9, 12)

mul3lとplus5lを連続してflatMapで適用すると以下になります。

scala> List(1, -2, 3, 4).flatMap(mul3l).flatMap(plus5l)
res32: List[Int] = List(17)

Kleisli

mul3lとplus5lの組み合わせが頻出する場合、これを一つの関数に合成しておくと便利です。ただし、mul3lやplus5lは引数と返却値の型が違うので単純な関数合成はできません。

scala> mul3l andThen plus5l
<console>:16: error: type mismatch;
 found   : Int => List[Int]
 required: List[Int] => ?
              mul3l andThen plus5l
                            ^

そこで登場するのがKleisliです。

"プログラムとはクライスリ圏の射である(program is arrow of Kleisli category)"という背景があり、クライスリ圏の射に対する演算をプログラミング言語で扱うメカニズムがScalaやHaskllのモナドということになるかと思います。

そして、クライスリ圏の射はScalaではA→M[B]の関数が対応します。A→M[B]の関数はMonadのflatMapメソッドに適用できますが、単体で扱いたい場合には特別なサポートがあると便利です。このような目的で使用するのがScalazのKleisli(以下単にKleisliと呼びます)です。

KleisliはA→M[B]の関数をくるんで、クライスリ圏の射として操作するためのメカニズムを提供します。

mul3lとplus5lをKleisli化すると以下になります。mul3lkとplus5lkがKleisli化したmul3lとplus5lです。

scala> val mul3lk = kleisli(mul3l)
mul3lk: scalaz.Kleisli[List,Int,Int] = scalaz.Kleislis$$anon$1@6119d27b

scala> val plus5lk = kleisli(plus5l)
plus5lk: scalaz.Kleisli[List,Int,Int] = scalaz.Kleislis$$anon$1@38ab3c51

mul3lkとplus5lkはKleisliなので演算子>=>で合成することができます。

scala> val m3p5lk = mul3lk >=> plus5lk
m3p5lk: scalaz.Kleisli[List,Int,Int] = scalaz.Kleislis$$anon$1@76f4a74b

scala> m3p5lk(1)
res40: List[Int] = List()

scala> m3p5lk(4)
res41: List[Int] = List(17)

合成したKleisliであるm3p5lkをflatMapに適用すると以下のようになります。このm3p5lkのようにA→M[B]をKleisli化して合成することでMonad向けの部品を整備することができます。

scala> List(1, -2, 3, 4).flatMap(m3p5lk)
res39: List[Int] = List(17)

なおKleisliは引数1の関数でもあるのでmapメソッドに適用することもできます。

scala> List(1, -2, 3, 4).map(m3p5lk)
res43: List[List[Int]] = List(List(), List(), List(), List(17))

パイプライン・プログラミング

Kleisliをパイプライン・プログラミングの観点でみてみましょう。

まずListからmapやflatMapメソッドを適用する形も処理が左から右に流れていくのでパイプライン・プログラミングといえます。これがMonadを使ったパイプライン・プログラミングです。

scala> List(1, -2, 3, 4).flatMap(mul3lk).flatMap(plus5lk)
res46: List[Int] = List(17)

さらに関数の合成としてのパイプライン・プログラミングとしてはパイプライン演算子とKleisliの合成を使って以下のような処理が可能です。

scala> 4 |> mul3lk >=> plus5lk
res45: List[Int] = List(17)

2012年8月7日火曜日

クラウド温泉3.0 (15) / Arrow

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

今回のテーマはArrowです。Arrowは圏論の射(arrow, morphism)を型クラス化したもので、圏(category)を型クラス化したCategoryと組合せて圏論的な操作ができると思われますが、具体的なユースケースはボクもよくわかりません。

しかし、引数1の関数であるFunction1を合成するという用途では一定の利用価値があります。

Scalazでの型クラスは定義は以下のような形になります。

関数の合成

まず3倍する関数mul3と5を加える関数plus5を定義します。

scala> val mul3 = (_: Int) * 3
mul3: Int => Int = <function1>

scala> val plus5 = (_: Int) + 5
plus5: Int => Int = <function1>

Scala純正のcomposeメソッドで合成することができます。

scala> val p5m3 = mul3 compose plus5
p5m3: Int => Int = <function1>

scala> p5m3(3)
res147: Int = 24

Scala純正のandThenメソッドで合成することもできます。andThenメソッドの方が左側の関数→右側の関数の順に関数が実行されるので、よりパイプライン的です。ただandThenというメソッド名が少し重たい感じです。

scala> val p5m3 = plus5 andThen mul3
p5m3: Int => Int = <function1>

scala> p5m3(3)
res147: Int = 24

ScalazのArrowを使うと関数合成を以下のように>>>演算子で記述することができます。

scala> val p5m3 = plus5 >>> mul3
p5m3: Int => Int = <function1>

scala> p5m3(3)
res147: Int = 24

パイプライン演算子|>と組み合わせると以下のようになります。まさにパイプライン処理という感じですね。

scala> 3 |> plus5 >>> mul3
res150: Int = 24

パイプラインの複線化

&&&演算子でパイプラインを複線化、***演算子でパイプラインの並行実行を行うことができます。また、firstメソッドで一番目のパイプラインのみの実行、secondメソッドで二番目のパイプラインのみの実行を行うこともできます。

scala> 3 |> (plus5 &&& mul3)
res43: (Int, Int) = (8,9)

scala> 3 |> (plus5 &&& mul3) >>> (plus5 *** mul3)
res44: (Int, Int) = (13,27)

scala> 3 |> (plus5 &&& mul3) >>> (plus5 *** mul3) >>> mul3.first
res45: (Int, Int) = (39,27)

詳しくは「関数型とデータフロー(2)」を参照してください。

Monadicプログラミング

本セッションではMonadicプログラミングは以下のものを指すことにしました。

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

Arrowによる関数合成も「パイプライン・プログラミング」であるMonadicプログラミングの重要な構成要素になります。

2012年8月6日月曜日

クラウド温泉3.0 (14) / Applicative

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

Functorよりも強くMonadよりも弱い性質を持つ型クラスにApplicativeがあります。正確にはApplicative Functorという名前がついていますが、型クラス名に合わせてここではApplicativeと呼ぶことにします。

圏論系の技術の中での位置付けは以下のような感じです。

Scalazでの型クラスは定義は以下のような形になります。

Scala Tipsの中では以下の記事が分かりやすいかもしれません。

Applicative

ApplicativeはFunctorやMonadと同様にオブジェクトを格納するコンテナかつ計算文脈です。Applicativeの集まりに対して、1つのApplicativeを1つのパラメタとして演算を実行することができます。

演算子|@|がScalazが提供する代表的なApplicative演算子です。

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

ApplicativeのListに対して以下のようにfoldによる畳込みを行うことができます。

scala> List(1.some, 2.some).foldRight(0.some)((x, a) => (x |@| a)(_ + _))
res104: Option[Int] = Some(3)

ApplicativeはTraversableとの組み合わせで畳込み処理を行うこともできます。ApplicativeはTraversableの対象となっているのが重要な性質の一つです。

scala> List(1.some, 2.some).sequence
res92: Option[List[Int]] = Some(List(1, 2))

scala> List(1.some, 2.some).sequence.map(_.foldRight(0)(_ + _))
res111: Option[Int] = Some(3)

ListやOptionあるいはValidationなど、Monad的なオブジェクトはMonadかつApplicativeであることがほとんどなので、プログラミング上はMonadの機能の一つと考えておくと分かりやすいでしょう。

使用例

ApplicativeはValidationと併用するのが典型的な使用例です。

ノート

Applicativeで行う処理の多くは条件が合えばMonoidでも実現することができます。Monoidを使った方が簡潔になるので、可能であればMonoidを使用するのがよいでしょう。

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

scala> List(1.some, 2.some).sequence.map(_.sum)
res96: Option[Int] = Some(3)

scala> List(1.some, 2.some).foldMap(identity)
res100: Option[Int] = Some(3)

scala> List(1.some, 2.some).sumr
res102: Option[Int] = Some(3)

ApplicativeとMonoidの使い分け、Monoidが使用できる条件判定をプログラミング時に瞬時に判断できるようにしておくのが大事です。

2012年8月3日金曜日

クラウド温泉3.0 (13) / Monad

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

Monadicプログラミングをテーマにしているので、主役はやはりMonadです。

Monadについては、Scala Tipsでも中心的なテーマとして取り上げてきたので、この中から今回のセッションの趣旨にそったものを集めてくる感じになります。

Monadicプログラミングをパイプライン・プログラミングと見立てると、以下のような記述方式が候補です。

この中でMonadを使っているのは3, 4, 5となります。パイプラインという観点からは、Monadを使っていないものを排除しても実用的なメリットはないので、合わせてMonadicプログラミングと呼ぶのが本セッションでの定義です。

Scalaにおけるモナドについては、以下の記事が参考になるかもしれません。

Monadの実装に用いられているScalazの型クラスは以下の記事で取り上げています。

Monadの具体的な使用方法として、以下のクラスを取り上げてきました。

モナドが提供する計算文脈を使うことで、計算文脈の提供するサービスを暗黙的に利用しながら動作する処理を、パイプラインのセマンティクスで処理を簡潔に記述することができます。

パイプラインの表層を流れるデータと演算、さらにパイプラインの裏側で暗黙に行われるデータと演算が疎結合になっているのが、モナドの醍醐味だと思うので、そのあたりにフォーカスしていきたいと思います。

ノート

パイプライン・プログラミングという観点からは重要な切り口ではないので、セッションでは取り上げないと思いますが、Scala TipsではfoldとMonadの組み合わせについても取り上げています。その他、Monadを対象としたメソッドが色々あるので、Scala Tipsで随時取り上げていきたいと思います。

2012年8月2日木曜日

クラウド温泉3.0 (12) / Monoid

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

関数型プログラミングで最重要な構成要素としてまずあげられるのがFunctorです。具体的にはmapメソッドを提供しているコンテナが相当します。これは空気のようなものですね。

そして、言うまでもなく最近の話題の中心はMonad。Scalaも言語仕様に取り入れています。セッションのテーマであるMonadicプログラミングも、このMonadをいかに活用するのかというのがひとつの軸になります。

この2つに加えて、個人的にこれも最重要構成要素に入れてよいのではと考えているがMonoidです。

Monoidは関数型プログラミングに頻出する抽象です。閉じた2項演算(つまりA☆A→A)でMonoidの規則を満たすものがあれば、Monoidとして定義しておくことでMonoid向けのさまざまなロジックをそのまま適用することができます。その代表的なものの一つがfoldによる畳込みです。

Monoidは残念ながらScalaの基本ライブラリでは提供されていないので、ScalazのMonoidを使うことになります。Scalazでは型クラスMonoidを提供しており、任意の型をMonoidとして定義することができます。

今まで記事でMonoidについて様々な観点から取り上げてきたので、この中から情報をピックアップしてスライドに集約する形になります。どれを持ってくるか悩みどころですね。

fold

Monoidの重要なユースケースであるfoldについて。foldとMonoidの組み合わせは関数型プログラミングのハイライトの一つかもしれません。Monadicプログラミングという観点からも、一つスライドを割り当てて取り上げることになると思います。

Reducer/Generator

Monoidの応用であるReducer, Generatorについて。Reducer自身は利用するシーンはあまり多くはないかもしれませんが、Monoidをどのような形で伸ばしていくのかというアプローチは参考になります。

2012年8月1日水曜日

クラウド温泉3.0 (11) / reduce, collect, flatMap

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

前回はFunctorが提供する計算文脈におけるパイプラインで使用できるmapメソッド、filterメソッド、foldメソッドを取り上げました。この3つが基本中の基本になりますが、他にも有用なものがあります。

reduce

reduceメソッドはコンテナ内の要素に対して二項演算を繰り返し適用し、ひとつの要素にまとめる処理を行います。foldメソッドの用途限定版です。

scala> List(1, 2, 3).reduce(_ + _)
res2: Int = 6

collect

collectメソッドは部分関数(partial function)を使って、要素の選択を変換を同時に行います。filterメソッドとmapメソッドを一つにまとめたような動きになります。

scala> List(1, 2, 3).collect {
     |  case x if x % 2 == 1 => x.toString
     | }
res5: List[java.lang.String] = List(1, 3)

flatMap

flatMapメソッドはモナドの動作を行うメソッドです。mapメソッドに指定する関数が要素の変換を行うのに対して、flatMapメソッドに指定する関数は要素からコンテナへの変換を行います。このコンテナの集まりを"コンテナの流儀"でひとつのコンテナにまとめるのがモナドの肝となる動きになります。

scala> List(1, 2, 3).flatMap(x => {
     | if (x % 2 == 1) List(x.toString, (x * 10).toString) else Nil
     | })
res7: List[java.lang.String] = List(1, 10, 3, 30)

Listの場合は、Listの集まりを順序を維持したまま一つのListにまとめます。このため、flatMapの実行でListの要素数を増やしたり減らしたりといったことが可能です。上記の例では、リストの要素数が3から4に増えています。

パイプラインの構成部品

パイプラインの構成部品の表にreduce, collect, flatMapを追加しました。

メソッド動作動作イメージコンテナ型要素型要素数
mapコンテナ内の要素に関数を適用して新しいコンテナに詰め直す。M[A]→M[B]変わらない変わる変わらない
filterコンテナ内の要素を選別して新しコンテナに詰め直す。M[A]→M[A]変わらない変わらない減る
foldコンテナをまるごと別のオブジェクトに変換する。M[A]→N変わる--
reduceコンテナの内容を一つにまとめる。M[A]→A変わる--
collectコンテナ内の要素に部分関数を適用して選択と変換を行う。M[A]→M[B]変わらない変わる減る
flatMapコンテナ内の要素ごとにコンテナを作成する関数を適用し最後に一つのコンテナにまとめる。M[A]→M[B]変わらない変わる増える/減る |

メソッドの追加に加えて、以下の点を変更しています。

  • コンテナ→コンテナ型、要素→要素型
  • コンテナ型が「変わる」の時は要素型、要素数は意味を持たないので「-」とした。

パイプラインで有用な部品のパターンは概ねカバーできたと思います。

ノート

スライド的には、この表で一つスライドを起こして、各メソッドを例題付きで簡単に説明する感じになりそうです。

foldとreduce

reduceはfoldの特殊な形なのでfoldファミリと考えるとよいでしょう。fold系の処理の中で条件によってreduceを選ぶという形になります。

foldファミリは、Monoidと相性が良いので、Monoidとどう組合せていくのかというのも重要な論点になります。これはMonoidで一つスライドを起こして説明したいと思います。

foldMap

flatMapメソッドは、まさにMonadicプログラミングの核となるメソッドです。

この比較表ではflatMapメソッドの威力はあまり見えてきません。これはFunctor視点の比較表になるためですね。flatMapについては別枠で説明することにすることになります。

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月19日木曜日

クラウド温泉3.0 (3) / 代数的データ型 on Scala

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

前回は代数的データ型について考えました。

ボクの非公式理解では、代数的データ型は:

  • 直積の直和の総和

です。

代数的データ型は以下のように、オブジェクト指向のインヘリタンスと対置する位置付けにある、関数型のかなり重要な構成要素であることが確認できました。

  • オブジェクト指向はクラスでインヘリタンス
  • 関数型は代数的データ型で選択

この代数的データ型ですが、Scalaではケースクラスで実装するのがイディオムになっています。

ケースクラス

以下のようにケースクラスPersonを定義します。これがそのまま代数的データ型となります。

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

代数的データ型は「直積の直和の総和」ですが、その単純形である「直積」そのものも代数的データ型です。ケースクラスはコンストラクタに並んだ要素の直積と見立てることが可能です。このためケースクラスを定義すれば、そのまま代数的データ型ということになります。

直積

Scalaでは直積を表すトレイトProductが定義されています。以下のように、ケースクラスPersonはProductでもあります。なんとケースクラスは意味上だけではなく、Scalaの型システム上も直積になっているというわけです。

scala> var a: Product = Person("Taro", 30)
a: Product = Person(Taro,30)

Scalaの代表的な直積の表現はタプルですが、このタプルも以下のようにProductとなっています。

scala> var b: Product = ("Taro", 30)
b: Product = (Taro,30)
選択

代数的データ型を「直積の直和の総和」として、直積そのものの実現はケースクラスで可能なことを説明しました。

それでは「直和の総和」はどう実現すればよいでしょうか。

これは、以下のようにsealed trait(またはsealed abstract class)とケースクラスの組み合わせがイディオムになっています。

sealed trait A
case class B() extends A
case class C() extends A
case class D() extends A

「直和の総和」の実現にインヘリタンスを使っていますが、オブジェクト指向的なインヘリタンスと違うのは、ベースクラスをsealedにすることで、サブクラスが後から追加されないことが保証されている点です。

このことで「直和」を表現しています。さらに複数のサブクラスを並べることで「直和の総和」を表現しています。

Scalaはオブジェクト指向の核の上に、関数型を載せている二段構成の言語システムになっているので、型システムはオブジェクト指向が核になります。このためオブジェクト指向の型システムを使って代数的データ型を実現すると全体の整合性が取れるということだと思います。

代数的データ型は選択と組み合わせて使用します。選択はScalaではmatch式で記述します。

先ほどの代数的データ型Aは以下のようにして選択を適用することができます。match式では「抜け漏れ」があるとコンパイル時に「match is not exhaustive!」の警告が出ます。

def f(a: A) = {
  a match {
    case b: B => xxx
    case c: C => xxx
    case d: D => xxx
  }
}
代数的データ型として気をつけること

前の説明で、「クラスのインヘリタンス」と「代数的データ型の選択」を対置しましたが、ケースクラスでは「代数的データ型の選択」の実現に「クラス(トレイト)のインヘリタンス」を使うというややねじれた構造になっています。しかし、これはScala言語の構成に適した実現方式ということであって、オブジェクト指向のクラスの機能を積極的に使うという意図ではない、という点は留意しておくとよさそうです。

ケースクラスは代数的データ型として使用するが想定されているクラスですが、文法的にそのことを強制する制約はないので、普通のクラスとして色々な機能を追加することができます。しかし、関数型プログラミングの中で代数的データ型としての用途を意識するケースでは、こういったオブジェクト指向的な機能の濫用は避けるのが賢明です。

列挙型

代数的データ型の応用として列挙型を表現する事ができます。この場合はケースクラスではなく、ケースオブジェクトを使うとよいでしょう。ケースオブジェクトを使うと、「Sunday()」ではなく「Sunday」という形で列挙型を使うことができるようになります。また、オブジェクトの生成を抑制する効果もあります。

ScalaではEnumというオブジェクトが用意されていますが、使い勝手が悪いので列挙型はケースオブジェクトを使うのを軸に考えるとよいと思います。ケースオブジェクトを使うことで「代数的データ型」的な効果(match式の抜け漏れチェックなど)も期待できます。

sealed trait DayWeek
case object Sunday extends DayWeek
case object Monday extends DayWeek
case object Tuesday extends DayWeek
case object Wednesday extends DayWeek
case object Thursday extends DayWeek
case object Friday extends DayWeek
case object Saturday extends DayWeek

列挙型の実現は、ケースオブジェクトではなく普通のオブジェクトを使うことも可能です。

sealed trait DayWeek
object Sunday extends DayWeek
object Monday extends DayWeek
object Tuesday extends DayWeek
object Wednesday extends DayWeek
object Thursday extends DayWeek
object Friday extends DayWeek
object Saturday extends DayWeek

ただ、ケースオブジェクトの方が以下のようなメリットがあるので、普通はケースオブジェクトにしておくとよいでしょう。

  • Productを継承して型システム上も直積つまり代数的データ型になる。
  • toStringで「Sunday」や「Monday」といった文字列が表示されるようになる。
  • Serializableになる。

スライド案

代数的データ型、ケースクラス、ケースオブジェクトについて考えてきましたが、Monadicプログラミングのテーマでセッション1時間に収めることを考えると、このあたりはほとんど触れることはできなさそうです。

「代数的データ型」のタイトルのスライドに以下のプログラムを載せておく感じかな。

case class Person(name: String, age: Int)

sealed trait A
case class B() extends A
case class C() extends A
case class D() extends A

sealed trait DayWeek
case object Sunday extends DayWeek
case object Monday extends DayWeek
case object Tuesday extends DayWeek
case object Wednesday extends DayWeek
case object Thursday extends DayWeek
case object Friday extends DayWeek
case object Saturday extends DayWeek

2012年7月18日水曜日

クラウド温泉3.0 (2) / 代数的データ型

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

関数型プログラミングの基本構成要素の一つに代数的データ型があります。Wikipediaによる定義は以下です。

ボク自身は計算機科学や数学の素養がないので、Wikipediaの定義が厳密には何を指しているのか十分に理解ができているとはいえないのですが、ボクと同じ立ち位置にいる非計算機科学系のプログラマの方には、ボクがプログラミング時に使用している非公式な理解が不正確なものであっても有益ではないかと思います。

その前提で、以下でScalaでの代数的データ型について考えます。

非公式な理解

ボクが代数的データ型を意識するときは、以下の定義を念頭に置いています。

  • 直積の直和の総和

直積は、「A×B×C」というように記述する複数の要素を組にしたものです。Scalaではタプルが直積の代表的な記述方式です。

直和は、「互いに交わらない、つまり共通部分が空集合であるような二つの集合の和集合」です。「A∪B」でAとBでダブリがない場合が該当します。ScalaではEitherが直和の代表的な記述方法です。

「直積の直和」は以下のようにイメージしています。

T = (A × B × C) ∪ (D × E)

直和は2つの和集合なので、これを複数の集合の和集合に拡張して考える必要があります。ここでは、これを総和と表現しています。

「直積の直和の総和」は以下のようにイメージしています。

T = (A × B × C) ∪ (D × E) ∪ (F × G × H)

「直積の直和の総和」であるTは以下の意味を持ちます。

  • Tは(A × B × C)の組、(D × E)の組、(F × G × H)の組のいずれかで、それ以外の値は持たない。

直和であることでダブリがないことに加えて「それ以外の値は持たない」となるわけですが、この性質が本質的に重要です。「それ以外の値は持たない」ので、選択処理で抜け漏れが発生することが絶対にないことを保証することができます。Scalaの場合は、コンパイル時にmatch式に対して「match is not exhaustive!」という警告を出すといった動きになります。

関数型プログラミングとオブジェクト指向プログラミング

最近のプログラミング言語は、関数型言語もオブジェクト指向言語も相互に影響を受けて便利そうな機能は同様に使えることが多く、境界線は曖昧です。例題レベルの簡単なプログラムだと、どちらも同じようにコーディングできることも多いでしょう。

このため、関数型プログラミングとオブジェクト指向プログラミングの本質的な違いは何かというのはかなり難しい問題ですが、JavaやScalaでプログラミングしている中で、情況証拠的に分かってきたのは「インヘリタンスの有無」がその本質ではないかということです。これは「動的に解決される多相性の有無」ということもできます。言葉を変えると、コンパイル時に抜け漏れが起きないことを保証できるのが関数型言語で、保証できないのがオブジェクト指向言語という表現もできます。

逆に、静的に解決される多相性は(オブジェクト指向でも有効ですが)関数型言語の守備範囲で、型クラスがこの代表的な機能になります。

もちろん、抜け漏れがあるオブジェクト指向言語が劣るとういうことではなく、動的にシステムを拡張できるという重要な性質の裏返しということです。プラグインをコンポーネントとして追加してアプリケーションを拡張する、といったアプローチはオブジェクト指向ならではということですね。

まとめると、オブジェクト指向では型(クラス)に毎に適用するロジックを切り替える処理はクラスのインヘリタンスによる多相性で実現します。これを関数型では以下のいずれかで実現します。

  • 代数的データ型に対する選択
  • 型クラス

型クラスはちょっとビッグな機能で日々のプログラミングで気軽に定義して使うものではないので、ざっくりといってしまうと、以下のように考えることができます。

  • オブジェクト指向はインヘリタンス
  • 関数型は代数的データ型で選択

そう考えていくと、代数的データ型は関数型プログラミングをする上で、かなり重要な機能であることが分かります。また、Scalaで関数型プログラミングを行う場合は、インヘリタンスは使わず「代数的データ型で選択」に持っていくことが正しい作法ということになりそうです。

Scala

と、ここからScalaの実装に入りたいところですが、長くなってしまったので次回にします。

2012年7月13日金曜日

クラウド温泉3.0@小樽

今年もクラウド温泉の季節がやってきました。

今回もかなり濃い内容になりそうで楽しみです。ustreamなしのオフレコなので、かなり踏み込んだ話を聞けるのもクラウド温泉の醍醐味ですね。

今回は2つほどスライドを用意する予定です。

  • Monadicプログラミング・マニアックス
  • OFP & OFADについて(仮)

連休明けぐらいから、この2つのスライドについて:

で行ったような感じで、準備がてらブログに内容をあげていきたいと思います。この時は、以下のようなまとめを作りましたが、今回も作ることになると思います。

Monadicプログラミング・マニアックス

「Monadicプログラミング・マニアックス」というタイトルにしていますが、内容的にはScalazを使った関数型プログラミング入門という感じになると思います。現時点でScalaとScalazを使っている事自体がマニア、ということで。

内容的には、去年のJJUGで使った「楽々Scalaプログラミング」を起点にMonadicプログラミングの技やプログラミング戦略を色々書いていこうと思います。

http://www.slideshare.net/asami224/scala-9728001

キーワード: モナド、モノイド、型クラス、代数的データ型、永続データ構造、並列プログラミング、SQL。

OFP & OFADについて(仮)

こちらは、二日目午後に小樽商大会議室で行うフリーディスカッション「OFP & OFADについて」のネタ投入用のスライドです。基本的には「Object-Functional Analysis and Design: 次世代モデリングパラダイムへの道標」をそのまま使う予定ですが、OFADについて少し書き足したいこともあるので内容を追加できればと思っています。

このスライド起点の議論はOFADよりのものを想定していますが、これとは別にOOPとFP、OFPを軸にした議論もあると思うのでそちらも楽しみです。OOPもJavaのように実用志向で手続き型寄りのものが主流になっていますが、元祖オブジェクト指向でメッセージングのメタファを持つSmalltalkやJavaScriptのようなプロトタイプベースなど色々なバリエーションがあります。

個人的には業務アプリのプログラミングパラダイムはOOPからOFPにパラダイムシフトしていくと思っているのですが、このタイミングでOOPやFPの原点に立ち戻って議論することで、新しいOFP像を垣間見ることができればと期待しています。