Prev: さらなる探究 TOC: 目次 Next: 付録 II - Haskell のコード例

モナドの物理的なアナロジー

モナドは非常に抽象的なエンティティなので、ときには、モナドそのもの について直接考えるより、モナドのアナロジーとしての具体的なシステムを 考えるのが有用です。この方法では、物理的な直観と経験を利用して、 計算モナドの抽象的世界を再考するための洞察を得られます。

ここで展開する特殊な物理的なアナロジーは機械化された組み立てラインです。 これは、モナドに完璧に合致するものではありません。— 殊にモナド 計算の高階性ある局面の幾つかでは。— しかし、初期段階では、モナドが どのように機能するかを理解するには十分役立ちます。

Haskell のプログラムをベルトコンベヤと考えることから始めましょう。 入力はコンベヤの端に載せられ、一連のワークエリアへと運ばれます。 各ワークエリアにおいて、ベルトコンベヤ上の物品になんらかの操作が 加えられ、そしてその結果はまたベルトコンベヤで、次のワークエリアに 運ばれます。最終的にはベルトコンベヤは最終品を組み立てラインの端まで はこびこれを出力します。

この組み立てラインモデルでは、モナドの物理的なモデルは、組み立てライン 上で、連続したワークエリアが、それぞれの機能を合成して、製品全体を つくりだすのを制御する機械システムということになります。

モナドは以下の3つの部分からなりたちます。

  1. ベルトコンベヤとともに移動する作りかけの製品を載せるトレー
  2. トレーに任意のオブジェクトを載せるローダ(loader)マシン
  3. 対象の載ったトレーをひとつとり、新しいオブジェクトの載った新しいトレーを 作る合成(combiner)マシン。これらの合成マシンは、実際に新しい オブジェクトを生成するワーカ(worker)マシンに取り付けられています。

組み立てラインを、最初に材料をトレーに入れるローダマシンとして セットアップしてモナドを使います。そして、ベルトコンベヤは、それらのトレー を各ワークエリアへと運びます。ここで、合成マシンはトレーを取り上げ、その 内容にもとづいてワーカマシンに送るかどうかを決定します。これを示している のは図 A-1 です。

図 A-1 モナドアーキテクチャを使った組み立てライン

モナドの組み立てラインに関して注意すべき要点は、ワーカマシンの出力 を合成する仕事を、ワーカマシンで実際に行われる仕事から分離しているという ことです。それらを分離してしまえば、独立して変更することができます。 それゆえ、同じ合成マシンをひとつの組み立てラインで飛行機を作らせることも 箸をつくらせることもできます。 同様に、ワーカマシンについても、おなじ もので、合成マシンを取り換えて、生産される最終製品を変更することが できます。

箸を作る組み立てラインの例を見て、それが物理的なアナロジーではどのように 動いているのか、それをどのように、Haskell のコードで表現するかを 見ましょう。 3 つのワーカマシンを作ることになります。最初のものは、木材の小片を入力と してとり、出力のトレーには、おおまかに箸の形をしたもののペアが載ります。 2 つめのワーカは、そのおおまかに箸の形をしたもののペアを取り、出力トレー には滑かなになり、磨かれて、レストランの名前が印刷された箸が一膳載ります。 三番目のワーカは磨かれた箸を一膳とり、出力トレーには、印刷済みの紙で 包装された箸が一膳載ります。これを、Haskell で書くと次のようになります。

-- 処理する基本のタイプ
type Wood = ...
type Chopsticks = ...
data Wrapper x = Wrapper x

-- 註: Tray 型は、Tray モナドからきています。

-- ワーカ関数 1: 箸の概形を作る
makeChopsticks :: Wood -> Tray Chopsticks
makeChopsticks w = ...

-- ワーカ関数 2: 箸を磨く
polishChopsticks :: Chopsticks -> Tray Chopsticks
polishChopsticks c = ...

-- ワーカ関数 3: 箸を包装する
wrapChopsticks :: Chopsticks -> Tray Wrapper Chopsticks
wrapChopsticks c = ...

これらのワーカマシンが箸を作るのに必要な機能をすべて持っていることは 明らかです。抜けているのは Tray モナド全体を構成するトレー、ローダ、 合成マシンの仕様です。トレーは空っぽであるか、もしくは、一つの品物が 載っているかのどちらかでなければなりません。ローダマシンは、単純に、 ひとつの品物をとり、それをベルトコンベヤに載せるだけです。合成マシンは 入力トレーをひとつずつとり、空でないトレーの内容を付属のワーカマシン フィードするあいだ空になったトレーを先へ送っておきます。Haskell では Tray モナドを次のように定義します。

-- トレーは空かもしくは品物が一つだけ載っていなければならない
data Tray x = Empty | Contains x

-- Tray はモナド
instance Monad Tray where
    Empty        >>= _      = Empty
    (Contains x) >>= worker = worker x
    return                  = Contains
    fail _                  = Empty    

Tray モナドが Haskell 98 の標準ライブラリの Maybe モナドの変装したものだということは分るでしょう。

のこるは、ローダマシンと合成マシンを使って、ワーカマシンを一列に並べ、 完全な組み立てラインを作るだけです。それは図 A-2 です。

図 A-2 完成した、モナドアプローチを使って箸を作るための組み立てライン

Haskell では直列化は標準のモナド関数で行うことができます。

assemblyLine :: Wood -> Tray Wrapped Chopsticks
assemblyLine w = (return w) >>= makeChopsticks >>= polishChopsticks >>= wrapChopsticks

あるいは、Haskell 組込みのモナドの do 記法を使って以下のように行えます。

assemblyLine :: Wood -> Tray Wrapped Chopsticks
assemblyLine w = do c   <- makeChopsticks w
                    c'  <- polishChopsticks c
                    c'' <- wrapChopsticks c'
                    return c''

ここまでで、モナドが組み立てラインを構築するフレームワークのようなものだ ということが判ったと思います。しかし、そのユーティリティについては ちょっと気をのまれるような感じがしたのではないかと思います。 なぜ、組み立てラインをモナドのアプローチで構築しようとするのかを 理解するためには、もし、工場のプロセスを変更したいと思ったとき、何が おこるかを、よく考えてみてください。

さて、ワーカマシンのどれかが上手く機能しないとき、fail ルーチンをつかって、空のトレーを作ることにしましょう。 fail ルーチンは失敗を説明する引数をひとつとります。 しかし、Tray 型はこれおを無視し、単に空のトレーを作ります。 この空のトレーは組み立てライン先まで進んでいきます。そして、 合成マシンはこれを残りのワーカマシンがバイパスするようにします。 最後はこの空のトレーは組み立てラインの最後に到達し、そこで、品質管理 エンジニアのもとに運ばれることになります。どのマシンが不調なのかを 探すのはそのエンジニアの仕事です。しかし、あるのは空のトレーです。

Tray モナドの今の fail ルーチンが無視している 失敗メッセージを上手く使えれば、この仕事がずっとやりやすくなるということ がわかりますよね。組み立てラインはモナドのアプローチで組織化してあるので ワーカマシンを変更することなく、この機能を追加するのは簡単です。

この変更を行うには、単には、空にはならない、新しいトレーのタイプを 作ればいいだけです。常に、品物が載っているか、品物が載っていない ちゃんとした理由のレポートがあるかのどちらかにします。

-- Tray2 は一つの品物か、失敗したときのレポートのどちらかを載せます
data Tray2 x = Contains x | Failed String

-- Tray2 はモナド
instance Monad Tray2 where
    (Failed reason) >>= _      = Failed reason
    (Contains x)    >>= worker = worker x
    return                     = Contains
    fail reason                = Failed reason

Tray2 モナドが Haskell 98 の標準ライブラリの Error モナドの変装したものだということは分るでしょう。

組み立てTray モナドを Tray2 モナドに置き換える だけで組み立てラインあっというまにアップグレードできます。今度は、 不具合が起きれば、品質管理エンジニアのところに運ばれて来たトレーには 不具合発生の原因についての詳細なレポートが載っています。


Prev: さらなる探究 TOC: 目次 Next: 付録 II - Haskell のコード例