Elm沼とKubernetes沼で交互に溺れているうちに1月が終わっていた

ElmCotoami のユーザーインタフェースを作るために選択したプログラミング言語で、そのデザインは JavaScript 界で一世を風靡している Redux の原型になったと言われている。その原型を体験してみたいという単純な好奇心だけで Elm を選択し、分かりやすいチュートリアルに感心したのも束の間、実際にアプリケーションを作り始めると、入り口ではあんなに優しかった Elm の顔がみるみる般若のようになり、気がついたら底なしの泥沼に足を取られていた。

Elm のような、いわゆる純粋な関数型と呼ばれる環境では、言語からコンピューターを直接操作することが出来ない。出来るのはデータの変換だけだ。この変換がいわゆる「関数」で、関数型でプログラマーに許されているのは関数を書くことだけである。しかし、コンピューターを操作出来ないと何も出来ないのでどうするのかと言うと、コンピューターへの命令をデータとして表現して、それを返す関数を作って環境に渡すみたいなことをする。

関数型にはこのような制限があるので、従来の命令型言語でプログラムを書く場合に比べて、かなり回りくどい書き方をしなければならない。しかしその代償として、プログラムは必ず「データの変換」という形に落とし込まれるので、実行時に意図しない動作をすることが格段に少なくなり、デバッグやテストも容易になるというわけだ。

関数型の回りくどさに加えて、Elm には厳密な型システムがある。実は今、開発スピードを減退させている最大の原因はこの型システムである。代数的データ型とか、今まで遭遇したことのないコンセプトに純粋な感動を覚える一方、曖昧さを許さない型システムのおかげで、型を合わせるためにどうすれば良いかというのを考えてるだけで膨大な時間が過ぎて行く。

動的型が、書く時にいかに楽をするか(少ない記述でいかに多くを実現するか)を主眼に置いているとすれば、静的型は、書いた後にいかに楽をするか(起こりうることを明示的に示しておく)ことに主眼を置く。しかし、動的型でも、曖昧に書いて楽をしつつも、危ないと思うところは慎重に書いたり、テストでフォローしたりするわけなので、単純に上のような図式が当てはまるとも思えず、どちらが良いと判断するのはなかなか難しい。動的型の言語であれば、場所によって手綱を締めたり緩めたり出来るのが、静的型だと一律で同じような書き方をしなければならない、というのがなかなか辛いところである。

まだ言語自体に慣れてないということもあるし、これから開発が進んで、規模が大きくなり、複数人で連携するようになったら関数型や型システムの恩恵を実感出来るのかもしれないなと思いつつ、最初の敷居はやっぱり高かったということだけここに記録しておく。

一方の、Kubernetes 沼の方はと言えば、こちらは Cotoami で試行錯誤した成果を仕事の方にフィードバックしたいということで、プロダクションへの導入を目指して色々と準備しているため、Elm よりもより深い深い泥沼となっている。Elm の場合はとりあえずユーザーインタフェースが動けばなんとかなるのに対して、インフラの場合はその辺のごまかしが効かないので、あらゆる側面から検証を行わなければならず、人材の不足も手伝ってかなり余裕のない状況に陥ってる今日この頃(もし、同じように Kubernetes で試行錯誤している方がいたら、情報交換したいなあと思うのですが… @marubinotto までご連絡お待ちしております(切実))。

Kubernetes は未来のインフラだとの意を強くする一方(やっと10年ぐらい前のGoogleに追いついただけとも言える)、アジャイルでない組織に導入してもメリットはあまりないだろうなとも思う。Kubernetes は単なるインフラというよりも、サービスがどのように開発・運用されるべきかという思想が強烈に埋め込まれた環境だと言った方が良いかもしれない。

Kubernetes によって、いわゆるデリバリーサイクルは極限まで短縮され、システムの柔軟性もかつてないレベルで実現出来るようになる。しかしその一方、運用やモニタリングにかかるコストは従来より高いように感じられるし、Kubernetes 自体も高速で進化していくので、そこにキャッチアップ出来る人材も確保する必要がある。例えば、運用において、MTTR (Mean-Time-To-Recovery) よりも、MTBF (Mean-Time-Between-Failures) の方を重視するような組織だと、なかなかこの機動性をメリットだと感じるのは難しいだろう。

既に開発が落ち着いていて安定運用されているサービスを Kubernetes に移行するメリットは、おそらくほとんどない。基本的には開発の初期から導入するのが望ましい。Cotoami はリスクのないオープンソース開発なので、その辺の事情で迷う必要がなく、一番始めのハリボテアプリの段階から Kubernetes で本番 ( https://cotoa.me/ ) の運用を始めて、自動デプロイなどの仕組みも整備出来た。

去年まで、ウチのチームでは、AMI (Amazon Machine Image) のように VM 上で動くホストがパッケージングの単位になっていた。パッケージを作るのも、パッケージをデプロイするのもうんざりするほど時間がかかる。それが Kubernetes/Docker によって、ホストの中で動く一つ一つのプロセスがパッケージングの単位となり、それらの小さな部品を組み合わせてホスト(Kubernetes では Pod と呼ばれている)を作り、それらをまた組み合わせて大きなWebサービスを作るという形に変わり、一度の更新も最小限で高速、しかも無停止ということで、新しい時代の到来を感じずにはいられない。

Elixir/Phoenix と Elm による関数型 Web 開発環境の構築

前回は、Cotoami のアーキテクチャについて、コレオグラフィ型を採用するという話を書いた。しかし、開発の最初からコレオグラフィを前提にした構成にするのはスモールスタートとは言い難いので、まずは核となるWebアプリケーションを作るところから初めて、徐々にイベント駆動の箇所を増やしてく感じで進めたい。

このWebアプリケーションを実装する環境として選んだのが、Phoenix FrameworkElm である。両方とも関数型の言語なので、Webアプリケーション全体を関数型の枠組みで実装することになる。

Elixirの強みについてはゆびてくで何度か触れているのでここでは割愛するが、Elm を選択したのは何故だろうか?

大きな要因としては、Elmアプリのアーキテクチャを参考にデザインされたという JavaScript のライブラリ Redux での開発経験が挙げられる。その過程で、複雑化するフロントエンドを実装する技術として、全てのビジネスロジックを「変換の連鎖」へと落とし込む関数型の有効性を実感した(参考: 関数型つまみ食い: 関数型とはプログラミング言語ではなく、プログラムデザインの問題であることに気づく | ゆびてく)。Elm の場合は、Redux では冗長になりがちだったこの仕組みを簡潔に表現出来る上に、Static Typing があるというのも大きなアドバンテージだと考えた。

エディタ上で即座にフィードバックを受けることが出来る
プログラムの誤りについて、エディタ上で即座にフィードバックを受けることが出来る

Phoenix と Elm の相性については、最近 Elm 側で Phoenix のサポートが入ったというのが明るい材料ではあるが… こればかりは試してみないと分からない。


[2016/12/09追記]

素晴らしいツッコミを頂く。


 
以下に Phoenix/Elmアプリケーションのひな形を作るまでの手順をまとめてみた。

 

関連ツールのインストール

Node.js

以下を参考に nvm をインストールする。

creationix/nvm: Node Version Manager – Simple bash script to manage multiple active node.js versions

  • Phoenixのサイトに「Phoenix requires version 5.0.0 or greater.」とある。

筆者の環境:

$ node -v
v5.4.1

 

Elixir

Installing Elixir - Elixir

Mac OS X で Homebrew を利用している場合。

$ brew update
$ brew install elixir

筆者の環境:

$ elixir -v
Erlang/OTP 19 [erts-8.0.2]  [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Elixir 1.3.4

 

PostgreSQL

標準構成の Phoenix が利用するデータベース。環境によってパッケージも様々なのでインストール方法については割愛。データベースを使わないのであれば省略可。

以下のコマンドでデータベース一覧が取得出来ればデータベースのスタンバイは出来ている。

$ psql -l

筆者の環境:

# SELECT version();
                                                              version                                                              
-----------------------------------------------------------------------------------------------------------------------------------
 PostgreSQL 9.4.0 on x86_64-apple-darwin13.4.0, compiled by Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn), 64-bit
(1 row)

 

Phoenix

Installation · Phoenix

$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez

筆者の環境:

$ mix phoenix.new -v
Phoenix v1.2.1

 

Elm

インストーラが用意されているので簡単。

Install · An Introduction to Elm

筆者の環境:

$ elm -v
0.18.0

 

Phoenix/Elm アプリケーションを作る

Phoenixアプリのひな形を作る

$ mix phoenix.new cotoami

依存関係の取得とデータベースの作成。

$ cd cotoami
$ mix deps.get
$ mix ecto.create   # PostgreSQLを使わなければ省略可
$ npm install

アプリを起動してブラウザでチェックしてみる。

$ mix phoenix.server

http://localhost:4000 にアクセスすると「Welcome to Phoenix!」のページが表示される。

 

elm-brunch をセットアップする

Phoenix に標準で付いてくる Brunch というJavaScriptのビルドツールがあるのだが、elm-brunch という Elm をビルドするための拡張があるのでそれをインストールする。

$ npm install --save-dev elm-brunch

brunch-config.js に elm-brunch の設定を追加。以下の二カ所を修正。

  1)
    ...
    watched: [
      "web/static",
      "test/static",
      "web/elm"
    ],
    ...

  2)
  ...
  plugins: {
    elmBrunch: {
      elmFolder: "web/elm",
      mainModules: ["App.elm"],
      outputFolder: "../static/vendor"
    },
    babel: {
      // Do not use ES6 compiler in vendor code
      ignore: [/web\/static\/vendor/]
    }
  },
  ...

 

Elmアプリのひな形を作る

$ mkdir web/elm && touch web/elm/App.elm
$ cd web/elm
$ elm package install elm-lang/html

App.elm の内容を以下のように編集。

module App exposing (..)

import Html exposing (Html, text)

main : Html msg
main =
  text "Hello Cotoami!"

 

ElmアプリをPhoenixアプリに配置する

Phoenixアプリのファイルをそれぞれ以下のように編集。

web/templates/layout/app.html.eex

web/templates/page/index.html.eex

<div id="elm-container"></div>

web/static/js/app.js に以下の二行を追記:

const elmDiv = document.querySelector("#elm-container")
const elmApp = Elm.App.embed(elmDiv)

これで準備は完了。ブラウザをリロードすると「Hello Cotoami!」と表示される。さらには、App.elm の内容を編集して保存すると、ブラウザが自動的にリロードされて即座に変更を確認出来るようになっているはずだ。

参考: Setting up Elm with Phoenix – Medium

組織やチームの話を追いかけると、プログラミングは基礎教養なんだって話に辿り着いてしまう件

まさにそんな感じの話がInfoQで紹介されていた。

関数型つまみ食い: 関数型とはプログラミング言語ではなく、プログラムデザインの問題であることに気づく

関数型プログラミングに精通していないプログラマにとって、「関数型」という言葉でまず思い浮かべるのは、Haskell や Erlang,Scala のような関数型言語や、Immutableなデータ構造、高階関数、ラムダ式、モナドと言った関数型プログラミングの道具立てだったりすることが多いのではないだろうか?

前回登場したゲームプログラマの James Hague 氏も、筆者と同じく、キャリア半ばで関数型の世界に足を踏み入れた越境者の一人だったが、その彼が、関数型言語 Erlang でのゲーム開発を経験して気がついたことがあると言う。それは、アプリケーション開発に純粋な関数(副作用のない関数)というコンセプトを導入することは、いわゆる関数型言語や関数型プログラミングとは全く関係ないということであった。

関数型というコンセプトの要は、ビジネスロジックを「データの変換」と捉えることにある。

functional1

関数というのは要するに「変換のルール」のことであるが、この関数が文字通り変換のルールのみを定義し(副作用がなく)、そして変換対象のデータを直接編集せずに、常に変換後のデータに丸ごと置き換えて行けば、システムの状態変化は常にアトミックに行われることになる。

functional2

このような形で、システムの状態遷移をシンプルにすることによって得られるメリットの大きさに、筆者の場合、関数型言語というよりもむしろ、Redux というフロントエンドのフレームワークを学ぶ中で気づかされた。

Redux はフロントエンドの状態管理を行うためのフレームワークである。オブジェクト指向に慣れているとアプリケーションの状態は個々のオブジェクトに分散するのが当たり前であるため、何故状態だけを独立して管理する必要があるのか、今ひとつピンと来ないこともあるかもしれない。GUIとオブジェクト指向というのは歴史的にも結びつきが強いため、この領域に関数型を適用するというのはどういうことなのか、これは筆者にとっても長年の疑問であった。

Redux は React というフレームワークとペアになっている。React はユーザーインタフェースをレンダリングするためのテンプレートエンジンで、入力データをテンプレートに当てはめて結果を出力するという、まさに関数としてデザインされている。そのため、テンプレート内で状態管理をすることは推奨されていない。状態は外部で管理して React 側に提供する必要がある。その状態管理を担うのが Redux である。

redux

Redux の仕組みは、上に書いたような関数型の状態管理そのままである。筆者にとって重要だったのは、これが JavaScript という、比較的慣れ親しんだ言語の上に構築されたフレームワークであったことだ。これによって「ああ、つまり関数型というのはフレームワーク(デザイン)なのだ」という気づきを得ることが出来た。

関数型デザインによって実現するシンプルな状態遷移のメリットは、Redux のプラグイン redux-logger によって出力されるログを見ると分かりやすい。

redux-logger

上のように、Redux で状態を管理していれば、そこで発生する全ての状態遷移は、

  1. prev state
  2. action
  3. next state

の繰り返しによってアトミックに行われる。何か問題が発生した時は、開発者自身があちこちにログを仕込んだりデバッガを活用せずとも、この状態遷移の内容を見れば何が起きたのか一目瞭然である。

Redux のドキュメントでは、関数型(functional)という言葉を出来るだけ使わずに説明を試みているようである。しかしながら、Prior Art にあるように、Redux の直系の先祖はブラウザで動く(ほぼ)純粋な関数型の環境 Elm であり、Elm は Haskell をベースにデザインされていることから、Redux は純粋関数型の思想を純粋関数型でない言語の上に実現したフレームワークだと言って差し支えないだろう。

関数型つまみ食い: モナドが難しいと思われている理由

(「モナドについて書かれたものを読む度に自分で書いてしまいたくなる。同じ過ちを繰り返すだけだから止めた方がいいんだけど。」という話を受けて、)

世の中に溢れる粗悪なモナド入門のせいで Philip Wadler氏の論文が埋もれているからモナドが難しいと思われるんだ。

ほほう、その論文を読めばモナドとやらを簡単に理解出来るのかな、と思いつつ読んでみると、

全然簡単じゃないんですけど…

でも、この論文でモナドの意義と基本的な仕組みはぼんやりと見えたような気もしなくはない。その理解をここに簡単にまとめておくことで、同じ過ちを繰り返してみることにしよう。

まずモナドを理解するためには、関数型プログラミングにおける、純粋な関数とそうでないものの区別をきちんとさせておく必要がある。

純粋な関数とは何か?

そもそも関数とは何か? それは、入力を出力に「変換」するものである。

関数: 入力 =(変換)=> 出力

至ってシンプルである。関数型プログラミングでは、この関数を組み合わせて、変換の連鎖としてアプリケーションを表現する。

さて、この関数が以下の二つの条件を満たすとき、その関数を「純粋」な関数と呼ぶ。

  1. 同じ入力に対しては常に同じ出力を返すこと
    • 例えば 1 を渡したら 2 が返ってくる関数があったとして、どのようなタイミングや事前条件で関数を実行しても 1 => 2 という同じ結果になること、そしてそれが他の入力パターンについても同様に成り立つこと。
  2. 関数は入力を出力に変換するものだと説明したが、その変換以外のことが一切起こらないこと
    • この変換以外の出来事を「副作用」と呼ぶ。

純粋な関数のメリットと痛み

Elixir試飲録 (4) – オブジェクト指向と関数型の違い」で触れたように、オブジェクト指向ではシステムの構成要素であるオブジェクトの「インターフェース」にフォーカスすることによって、システムの表現を単純化し、さらには疎結合による柔軟性を獲得していた。しかし一方で、インターフェースに現れない内部的なデータの流れは隠蔽されるため、インターフェースだけでシステムの(厳密な)状態遷移を把握することは困難になる。

object

少ない記述で多くを実現しようとするオブジェクト指向に対して、関数型、特に純粋関数型では、原理的に関数(入出力の変換)として書かれたことしか起こらないので、不測の事態が起こりづらく、より信頼性の高いシステムを構築しやすいと言える。しかし、逆に言えば、全てのデータの流れを入出力の変換として明示的に表現しなければならないので、時にはコードが冗長になってしまうケースがあるだろう。

function

この関数型の欠点とも呼べる冗長さを軽減するために導入されたのが「モナド」と呼ばれるコンセプトである。

モナド概念の導入

「関数型の欠点とも呼べる冗長さ」とは具体的にどのようなものだろうか? 以下のような例で考えてみる。

ここに「倍返し」という関数がある。名前の通り、受け取った数値を倍にして返す関数である。

倍返し: 数値 =(変換)=> 数値

この関数に 1 を渡すと 2 が返り、2 を渡すと 4 が返る。

この関数が「受け取った数値を倍にする」ことだけに専念しているとき、この関数は純粋であると言える。

しかし、世知辛い現実世界では、単に「倍返し」の純潔を守っているだけでは生き残って行けない。色んな要求が舞い込んでくる。例えば、倍返しに失敗したらどうしてくれるんだ?、だったり、倍返ししたことをちゃんと記録しておけよ、等々。そこで以下のような要求があった場合、どのように対応すれば良いだろうか?

  1. エラーが発生したら、それをきちんと知らせて欲しい
  2. 何回「倍返し」したのか記録しておいて欲しい
  3. 倍返しする度にログを吐いて欲しい

もし、関数が純粋でなくても良いなら、話は簡単である。エラー処理については、出力以外にも「例外」という経路を設けて、そこからエラーを知らせればよい。実際に多くのプログラミング言語ではこの方式を採用している。倍返し回数の記録についてはグローバルな変数を用意してそれをカウントアップして行けばよいし、ログについては深く考えることなく、普通にログを吐けばいい。

しかし、関数を純粋に保ったまま、つまり純粋な関数のメリットを維持したまま、このような機能を実現するためにはどうすればよいだろうか? 単純に考えると、出力にそのような情報を追加するしかない。

倍返し1: 数値 =(変換)=> 数値, エラー
倍返し2: 数値 =(変換)=> 数値, 回数
倍返し3: 数値 =(変換)=> 数値, ログ

エラーを投げたり、回数を記録したり、ログを吐いたりするのは「副作用」に当たるので、関数の純粋性を守るためには、これらの処理は「予定」として、関数の出力に含めて表現することになる。

これは明確さという観点から言うととても分かりやすい。関数の定義を見ればどのようなことが起こるのかが一目瞭然だからである。しかし一方で、関数の定義に、本来の処理「倍返し」以外の情報が紛れ込んでくるし、要求が増える度にいちいちこのような関数のバリエーションを増やして行かなければならないのだろうか?

そこで、このような「入出力に紛れ込む副作用」を本来の処理から隠蔽して冗長さを軽減するために導入されたのが、いわゆる「モナド」である。

「倍返し」の例を一般化すると、

倍返し: 数値 =(変換)=> 数値, おまけ

となるが、この関数に、どのような「おまけ」があるにしろ、そのことを意識せずに一番純潔な

倍返し: 数値 =(変換)=> 数値

として扱えれば便利である。そのために必要となるのが以下の仕組みである。

  1. まず前提としておまけ付きの出力: [本来の出力, おまけ]
  2. 本来の出力をおまけ付きに変換してくれる仕組み: 出力値 => [本来の出力, おまけ]
  3. おまけ付きの値を、そのままおまけ無しの値として関数に渡せる仕組み: [本来の入力, おまけ] => 本来の入力

この三つの組み合わせを「モナド」と呼ぶ。これらの道具立てがあれば、関数本来の計算がおまけに埋没することなく、かつ関数の純粋さを保つことができるというわけである。


[2017/05/18追記]

久しぶりにこの記事を読んでみて、もう少し説明を加えればモナドに対する理解がよりクリアになるような気がしたので、試しに書き加えてみる。

モナド三兄弟

関数型プログラミング(特に純粋関数型)は、システムの状態遷移を余すところなく関数の入出力として表現することで、システムの動きを明確にしようとする。全てを入出力として表現するので、ビジネスロジックにとって重要な「本来の入出力」以外の「おまけの入出力」がどうしても必要になってくる。

つまり、関数と関数の間を受け渡されていくデータには、常に何らかのおまけが含まれている可能性があるということだ。そのおまけは、例えば、処理に失敗した際のエラー情報だったり、値が存在しないかもしれないという可能性だったり、あるいは入出力には現れない文脈的なデータだったりする。このおまけの応用範囲がとても広いというのが、モナドのつかみどころのなさの一因になっていることは間違いなさそうだ。

しかし、関数の方はこのおまけを解釈するようにはできていないかもしれない、あるいはむしろ、特定のおまけを想定して関数を書いてしまうと、その関数の汎用性が大きく損なわれてしまうので、できれば本来必要な計算だけに集中したい。

そこで、おまけを想定しない関数を、おまけ付きの入出力を持つように変換するアダプター的な仕組みがあれば、本来の計算とおまけを切り離せる。この仕組みがモナドである。

より正確に言うと、この変換には三つのタイプがあり、その中の一つがモナドと呼ばれている。

具体的には、

a) 値 =(変換)=> 値  (全くおまけを想定してない関数)
b) 値 =(変換)=> 値, おまけ (おまけ付きの値を返す関数)
c) (値 =(変換)=> 値), おまけ (関数自体におまけが付いてる!?)

のような関数を、あたかも、

値, おまけ =(変換)=> 値, おまけ

のように扱えるようにしたい。この変換に対応するアダプターをそれぞれ、

a. Functor (ファンクタ)
b. Monad (モナド)
c. Applicative (アプリカティブ)

と呼ぶ。

ところで、上記三つの名前を見て、それぞれの機能が想像できるだろうか? これらは数学の「圏論 (category theory)」という分野由来の言葉で、そのままで意味をイメージできる人はかなりの少数派ではないかと思う。この名前の問題も、モナドが難しいというイメージを与える一因になっている。

そこで、以下のようにもっと分かりやすい名前で呼ぶようにしたらどうかという提案がなされているようだ。

  • Functor -> Mappable
  • Monad -> Chainable/Joinable/FlatMappable
  • Applicative -> Zippable

厳密にはこれらの語が意味するところとは異なるので(圏論的なニュアンスが抜け落ちるので)適切ではない、というのが専門家の見解であるようだが、これらの概念を理解する際の参考になることは間違いない。

まず Functor の Mappable という別名はとても分かりやすい。おまけ付きの値でも、[値 =(変換)=> 値] の関数でマッピングできるようにしてくれるものが Mappable というわけだ。

Functor で最もポピュラーだと思われるものが、値が「繰り返し」というおまけを持つ場合(これはつまりコレクションのこと)に対応する Functor だ。この Functor は、まさに map という名前で、多くの言語に導入されてお馴染みになった。コレクションを別のコレクションに変換するあの関数だ。この map に渡される変換用の関数は、値がコレクションの中に入っているという「おまけ」を気にすることなく、単に一つの値を別の一つの値に変換することだけに集中することができる。「本来の計算とおまけの分離」である。

Monad の Chainable も分かりやすい。

値 =(変換)=> 値, おまけ

という関数をつなげて (Chaining) 使いたい場合に、入力を変換するものが Chainable になる。

Applicative – 複数の入力値を取る関数をおまけ付き入出力に対応させる

Applicative はちょっとややこしい。これは、複数の入力値を取る関数に Functor 的な変換を行う際に使う。これまでの話では、あくまで一つの入力値を取るケースだけを想定して話をしてきた。

例えば、以下のように二つの入力を取る関数があるとする。

A, B =(変換)=> C

関数型プログラミングをサポートする言語の一部には、関数の「部分適用」という仕組みがある。これは複数の入力を受け取る関数があるときに、その中の一つの入力だけを渡して、その入力を受け取った状態の新たな関数を作る機能だ。

具体的に見てみよう。上の関数に、一番目の入力、A だけを与えると、入力を B 一つだけ受け取り、C を返す関数ができる。

A
↓
A, B =(変換)=> C
↓
B =(変換[A入力済み])=> C

さて、この部分適用という仕組みを踏まえて、二つの入力を取る関数におまけ付きの値を渡すためにはどうすれば良いだろうか?

まずは一つ目の入力値を Functor で渡してみよう。

A, おまけ
↓
A, B =(変換)=> C
↓
(B =(変換[A入力済み])=> C), おまけ

Functor は、入出力をおまけ付きに変換するので、部分適用して出来上がった関数がおまけ付きになってしまった。

というわけで、ここで Applicative の登場である。

すでに書いたように、Applicative は、

c) (値 =(変換)=> 値), おまけ

値, おまけ =(変換)=> 値, おまけ

に変換してくれる。つまり、

B, おまけ
↓
(B =(変換[A入力済み])=> C), おまけ
↓
C, おまけ

となって、最終的に欲しかった「C, おまけ」を手に入れることができた。

つまり、これら(Functor と Applicative)を組み合わせると、

A, おまけ and B, おまけ
↓
A, B =(変換)=> C
↓
C, おまけ

という感じで、複数の入力値を取る関数をおまけ付き入出力に対応させることができる、というわけである。

Elixir試飲録 (4) – オブジェクト指向と関数型の違い

古いブックマークを漁っていたら、オブジェクト指向と関数型の違いに関する面白い記事を見つけた。

記事は Michael Feathers 氏の興味深いツイートから始まる。

  • 試訳
    • オブジェクト指向は、可変部分をカプセル化(隠蔽)することによって、コードを分かりやすくする
    • 関数型は、可変部分を最小化することによって、コードを分かりやすくする

筆者なりに解釈すれば、オブジェクト指向はインターフェースにフォーカスし、可変部分を捨象することによって複雑なシステムを単純化し、関数型は副作用のない関数を組み合わせることによってシステムの可変部分を最小限に保ち、システムの動的な性質を掴みやすくしようとする。

オブジェクト指向の「可変部分を捨象する」という考え方がシステムを単純化する一方、インターフェースの裏側で起こることは予測し辛くなる。インターフェースの「契約」に現れない状態変化、極端な例を挙げれば「重要なデータを削除しないこと」など、それら全てについて自動テストを用意するのは現実的ではないため、オブジェクト指向のシステムが提供出来る信頼性には自ずと限界があることになる。

そして興味深いのは、オブジェクト指向の上に関数型の仕組みを乗っけるのは止めた方が良いという主張だ。この記事を書いた John D. Cook 氏は、関数型のメリットを享受したいのなら、まずは関数型だけに集中し、プログラムの大きなパーツ(モジュールなど)を管理する時にオブジェクト指向的な枠組みを利用すれば良いのではないかと書く(「functional in the small, OO in the large」)。

コードの中に、関数型の部分とそうでない部分が混在してしまうと、全体としては関数型の恩恵を受け辛くなるのだろう。この話題はこれからもちょくちょく出てくると思うが、Scala と Elixir の比較という観点からも興味深い指摘である。Scalaは基本的にオブジェクトから出発して関数型の流儀を徐々に入れて行くの対し、Elixirはまず関数型から出発する。そして、そこから関数をグルーピングするための Module や、Moduleにインターフェースの仕組みを導入するための Behaviour、そしてポリモルフィズム(多態)のような仕組みを実現する Protocol などが出てくる。

James Hague 氏は、100%純粋な関数型を実現するのは非現実的であり、大体85%ぐらいを目指せばいいのではないかと主張する。そして、残りの15%は注意深く分離してコードの中に分散しないようにすべきであると。

副作用がある部分を切り離すデザインは、Phoenix Frameworkで採用されているデータアクセスのフレームワーク、Ectoの中にも見られた。Ectoでは副作用のない Model と、副作用を担当する Repository を分けるという、いわゆる Repositoryパターンが採用されている。Railsの Active Record が Model 自身に副作用を内包するのとは対照的である。

Elixir試飲録 (5) – Elixir/Phoenixのホットデプロイ完全自動化(2016年1月版)

Elixir試飲録 (2) – カルチャーショックに戸惑う: 並行指向プログラミング

昨夜、Dave Thomas氏の「Programming Elixir」を読み終えて、こうしてブログを書いている。

読み終えて「とんでもないところに足を踏み入れたな」と思う。漂うのは、何もかもが今までのプログラミングモデルとは違うという圧倒的なアウェイ感である。

Dave Thomas氏は度々このブログにも登場しているが、『達人プログラマー』の著者であり、アジャイルソフトウェア開発宣言の発起人の一人でもある。近年はRubyの人という感じであったが、ElixirにRuby以来の衝撃を受けて、この「Programming Elixir」を執筆するに至ったとのこと。

この本が素晴らしいのは、元々はオブジェクト指向のプログラマーであった著者が関数型言語であるElixirに出会って、徐々にそのコンセプトに体を慣らせて行く過程を、読者が追体験できるところである。この本で扱っているのは Elixir やその基礎となっている Erlang/OTP のほんの導入部分のみであり、主眼はオブジェクト指向プログラマーが関数型あるいは並行指向プログラミングへとパラダイムシフトするための手助けをするといった趣向になっている。

「Programming Elixir」のサンプルプログラムを動かしながら一通りのトピックを見て回り、Elixir(というかErlang)で最も重要なのは、関数型プログラミングではなく、「並行指向プログラミング」と呼ばれる、そのプロセス管理の仕組みであることが分かった。関数型言語はあらゆるプラットフォームで利用出来るが、この並行処理のモデルはおそらくErlangにしかないものなのだろう。

Elixirの並行処理の威力を見るにはサンプルコードを実行するのが手っ取り早い。

まず、Elixirのサイトにある一番始めのサンプルコード(Hello world)が、いきなり並行処理の例になっている。

parent = self()

spawn_link(fn ->
  send parent, {:msg, "hello world"}
end)

receive do
  {:msg, contents} -> IO.puts contents
end

これを hello.exs というファイルに保存して、

$ elixir hello.exs 

のように実行すると、

hello world

となる。

Elixirを知らない人でも、このコードが何をしているかは、何となく分かるのではないだろうか。

  1. spawn_linkという関数に渡された関数が、このプログラムを実行しているメインのプロセスとは別のプロセスとして起動されて、関数の内容を実行する。
  2. 新しく作られたプロセス側では、メインプロセス側(parent)に “hello world” というメッセージを送る。
  3. メインプロセス側は、どこからかメッセージが来ないかを待ち受けて(receive)、メッセージが来たらそれをコンソールに表示する。

このように、spawn でプロセスを作り、sendreceive でメッセージのやり取りをするという単純な仕組みである。プロセスの間でのやり取りはこのメッセージ交換だけで、可変データを共有するということがないので、従来の並行処理について回る排他処理などが必要にならないというのが Elixirの強みである。

この例では一つのプロセスを作っただけなので、他の言語でも同様のことは簡単に出来るかもしれない。では、以下の例だとどうだろうか。

defmodule Chain do
  def counter(next_pid) do
    receive do
      n -> send next_pid, n + 1
    end
  end

  def create_processes(n) do
    last = Enum.reduce 1..n, self,
      fn (_, send_to) -> spawn(Chain, :counter, [send_to]) end
    send last, 0
    receive do
      final_answer when is_integer(final_answer) ->
        "Result is #{inspect(final_answer)}"
    end
  end

  def run(n) do
    IO.puts inspect :timer.tc(Chain, :create_processes, [n])
  end
end

いきなり複雑な例になったが、やっていることは単純である。run関数が呼ばれると、指定された数(n)だけのプロセスを次から次に立ち上げて、それらのプロセスには順番に次のプロセスへの参照を持たせて数珠つなぎのような構造にする(終端はメインプロセス)。全てのプロセスを立ち上げたら、先頭に数値「0」を送ると、プロセスの間でバケツリレーをしながら、その数値に「1」を加えて行く。最後はメインプロセスに到達して結果の値をコンソールに書き出す。この一連の処理は、Erlangの関数である :timer.tc によって時間が計測されており、結果とともに経過時間も出力される。

さて、このコードを chain.exs というファイルに保存して、以下のように実行したらどうなるだろうか?

$ elixir --erl "+P 1000000" -r chain.exs  -e "Chain.run(1_000_000)"

生成するプロセスの数はなんと100万個である。デフォルトのセッティングでは同時並行プロセス数の上限に達してしまうので、オプション(--erl "+P 1000000")で上限を変更している。

これを筆者のPC(Macbook Pro – 3 GHz Intel Core i7, 16GB RAM)で実行した結果が以下である:

{7011658, "Result is 1000000"}

一つ目の数字の単位はマイクロ秒なので、実に100万個のプロセスを起動して連携させる処理がたったの7秒程度で終わったということになる。

何故このようなことが可能なのかと言えば、これらのプロセスがOSで管理されているプロセスやスレッドなどではなく、Erlang VMの中で管理されている独自のプロセスだからである。このErlangが提供する極めて軽量なプロセスモデルが、Elixir/Erlang環境が単なるプログラミング言語以上のインパクトを持つ所以となっている。

このようにElixirでは、一つのプロセスが極めて軽量なため、一つのプロセスを作るのは、オブジェクト指向システムで一つのオブジェクトを作るのと同じぐらいの感覚であると「Programming Elixir」では説明されている。実際プロセスの中には状態を持つものもあり、それらがメッセージでやり取りをするとなれば、モデル的にはオブジェクトと変わりないように思える。Elixirでは、状態を持つプロセスを「Agent(エージェント)」として容易に管理する仕組みがあり、これは、かつてポスト・オブジェクト指向の有力候補と呼ばれていたエージェント指向ではないかと、はたと思い当たったが、それは多分違うような気がする。

Elixir試飲録 (3) – マルチコア危機によるパラダイムシフト: オブジェクト指向から並行指向へ