エンジニアとモナド 
ruicc
だれ 
• @ruicc (Kousuke Ruichi) 
• Serverside Engineer 
• Haskeller
“モナドは単なる自己関手の圏におけるモノイド対象だよ。 
何か問題でも?” 
–Philip Wadler
“大問題だよ!!!” 
–An Engineer
未だみんながモナドで迷ってる
このスライドの方針 
• エンジニア視点からMonadを解説する 
• Monadを用いた設計方針の提案
必要な前提知識 
• Haskell 
• IOモナドが使える程度
エンジニアから見たMonad 
Monadとは 
非常に軽いEDSL
DSLの特性(簡略版) 
• DSLは問題を解く際に、非常に便利 
• ただし構築コストが非常に高い
EDSLが5分で構築可能だと 
俺たちにとって何が嬉しいか
Agenda 
• Embedded DSL 
• Monad 
• 問題に対するアプローチ 
• Monadを用いて問題を解く
Embedded DSL
DSLとは 
• Domain Specific Language, ドメイン特化言語 
• General-purpose (Programming) Language, 汎用言語
DSLの4要素 
• コンピュータプログラミング言語 
• 言語特性 
• 限定された表現力 
• ドメインへの集中
内部DSLと外部DSL 
• 内部DSL(Embedded DSL) 
• 実装言語と同じ構文 
• 外部DSL 
• 実装言語と異なる構文
DSLを何故使うか 
• 生産性の向上 
• ドメインエキスパートとのコミュニケーション 
• 実行コンテキストの移動 
• 代替計算モデル
DSLの問題点 
• 言語間不協和 
• DSL作成コスト 
• 非常に高い、DSL作成が必要か見極めが要る 
• ゲットー問題 
• 視野を狭める抽象概念
EDSLを構成するもの 
• Primitive operations(以下primitivesとする) 
• 関数 
• 型 
! 
• DSLはプログラミング言語なので、構成要素自体は同じ
EDSLまとめ 
• 能力限定されたプログラミング言語 
• 構築が軽いと嬉しい 
• DSLと外部の連携が簡単だと嬉しい
Monad
Monad定義 
• MonadとはMonad則を満たすものである 
• ここではHaskellのMonad型クラスを指す 
class Monad m where 
return :: a -> m a 
(>>=) :: m a -> (a -> m b) -> m b
Monadとは設計問題である 
• 単にIO Monadだけを用いていても問題はない 
• IO Monadは命令型言語
Monadとその周辺 
• Monad instanceとなる型 
• Monad instance定義 
• Monad固有のactions 
• run関数
Monad instanceとなる型 
• IOで良い、もしくはそれをnewtypeでラップした型 
• DSLでプログラムそのものを意味する 
newtype MyMonad a = MyMonad { unwrap :: IO a } 
deriving (Functor, Applicative, Monad, MonadIO)
Monad instance定義 
• IOの場合はすでにMonadなので不要 
• newtypeでMonadをwrapした場合もderivingで導出出来 
る 
instance Monad MyMonad where 
return a = MyMonad $ return a 
m >>= k = MyMonad $ do 
a <- unwrap m 
unwrap $ k a
Monad固有のactions 
• Monadは、一般に固有のactionsを持っている 
• DSLのPrimitivesに対応 
• action1 :: M () 
• Monad M 上でなんらかの副作用を起こす 
• action2 :: A -> B -> M C 
• 引数A, Bをとり、Monad M 上で結果Cを得る
run関数 
• Monadを「走らせる」関数 
• DSLの実行関数に対応 
• Monadをreturn, (>>=)で構築した後、一回だけrunするこ 
とが出来る
“特殊”なMonad 
• Monadの例でMaybe, Listあたりをよく見るが、それら 
Monadは”特殊”なMonad 
• Maybe, List 
• actionsが存在しない 
• runせずともパターンマッチで中身が取り出せる 
• IO 
• actionsが非常に沢山存在する 
• run関数はHaskellプログラムの実行に等しい
“一般的”なMonad, State 
• 一般的なMonadの簡単な例としてはStateが適当 
newtype State s a = State { runState :: s -> (a, s) } 
instance Monad (State s) where 
return a = State $ s -> (a, s) 
m >>= k = State $ s -> let 
(a, s') = runState m s 
in runState (k a) s'
State Monad 
• 固有actions 
• get :: State s s 
• put :: s -> State s () 
• run関数 
• runState :: State s a -> (s -> (a, s))
Monad Transformer 
• モナド変換子 
• Monadをwrapして機能を一つ追加する 
• wrap後もMonadになる 
• Monad Transformerを何重にも積み上げる様は“Monad 
Stack”と呼ばれる
Monad Transformerとrun 
• Monad StackはMonadと複数個のMonad Transformerが 
重なって出来ている 
• “Monad Stack”の名前の通り、FILOの順 
• runするときは、外側(上)から順番に「走らせる」
Monad Transformer例 
• パラメータのValidity checkをMaybeT 
• 失敗したら終わり 
• プロトコルをMaybeTで記述 
• 失敗したら終わり
Monad Instanceの抽象化 
• 例えばReaderクラスの抽象化 
• class Monad m => MonadReader r m 
• 慣習としてMonad* と付けることが多い
問題に対するアプローチ
Top-DownとBottom-Up 
• Top-Down 
• システムを分割することで問題を解く 
• 問題を直に解いていくが、詳細部分が壊れがち 
• 保守時につらい 
• Bottom-Up 
• 小さなパーツを組み合わせてシステムを構築する 
• 下周りはうまく書けるが、問題を解くまで時間がかかる 
• 開発時つらい
関数とBottom-Up 
• 組み合わせる手段を豊富に持つ関数 
• 小さな関数を組み合わせて複雑な問題を解く 
• Bottom-Upの解き方になる 
• 典型例:Parser Combinators
オブジェクトとTop-Down 
• 「問題を解く者」の抽象としてオブジェクトを捉える 
• システムのモデリングのためにオブジェクトを複数定義 
し、タスクを任せる 
• 自然Top-Downアプローチを採る
関数型言語とTop-Down 
• 問題をザクザク解きたい 
• どうする? 
• DSLで問題を切り取る
Monadを用いて問題を解く
DSL定義問題概要 
• DSL定義問題 
• どのように問題を区切るか 
• 問題を分割する問題 
• どのように分割された問題を扱うか 
• DSL外部の問題 
• DSLの外部への接合 
• どのようにDSLのprimitivesを定義するか
問題を定義する 
• DSL定義は問題を切り取る行為である 
• 問題を先に定義しておくことが望ましい 
• Haskellは関係ないエンジニアの仕事
DSLを定義する 
• DSLの定義はprimitivesの定義 
• 一つの問題に対して、複数のprimitives定義が考えられる
DSLをHaskell上で実装する 
• 基本的にはMonadのサブクラスでいい 
class Monad m => MonadMyDSL m where 
getLine :: m Text 
echo :: Text -> m () 
• Monad InstanceはIOで問題ない 
instance MonadMyDSL IO where …
DSL上のロジックの記述 
• 以下の様に多相的に記述する 
• foo :: MonadMyDSL m => Text -> m (Text, Int) 
• bar :: MonadMyDSL m => Int -> Text -> m () 
• DSL m の仮定下においては、DSL m のprimitivesしか使 
えない 
• Haskellの型制約を利用したDSLの完結
多相化に伴う 
パフォーマンス問題 
• GHC PRAGMAの一つ、SPECIALIZEで対処する 
• foo :: MonadMyDSL m => m Text 
• {-# SPECIALIZE foo :: IO Text #-} 
• 実質IOで動作する
過度の多相化問題 
• Haskellは型クラス等による過度の多相化問題がしばしば 
初心者の壁となる 
• 対処法 
• 関数定義時は多相化してよい 
• 関数呼び出し時は明示的に単相化する
プロトタイピング 
• 簡単にロジックを組んでみて、primitivesが適切かどうか 
を評価するといいかもしれない
効率の良いDSLを定義する 
• DSL設計段階ではprimitivesは最低限存在すればよい 
• 場合によっては動的性能改善のために、primitivesを追 
加しても良い
DSLのネスト 
• DSL1つでは問題が広すぎる場合、別途DSL内部を定義し 
ても良い 
• 方針を2つ提示する 
• Monad TransformerでMonad Stackを重ねる 
• IO/ST/STM/Identityを経由する
TransformerによるDSLネスト 
• newtype BaseDSL a = BaseDSL { unBDSL :: IO a } 
• newtype EmbDSL' m a = EmbDSL' { unEDSL’ :: m a } 
• type EmbDSL a = EmbDSL’ BaseDSL a 
• instance MonadTrans EmbDSL’ where … 
! 
• runEmbDSL :: EmbDSL a -> BaseDSL a 
• runEmbDSL = unEmbDSL’
IO/ST/STM/Identityを 
経由したDSLネスト 
• IO/ST/STM/IdentityはMonad Stackの底に位置し得る 
• newtype BaseDSL a = BaseDSL { unBDSL :: IO a } 
• newtype EmbDSL a = EmbDSL { unEDSL :: IO a } 
! 
• runEmbDSL :: EmbDSL a -> BaseDSL a 
• runEmbDSL = liftIO . unEDSL
既存のMonadの上でDSLを定義 
• Library/Frameworkで既にMonadが提示されている場合 
など、既存のMonadを使う必要があるケースがある。 
• E.g. Yesod 
• 対処法は先と同じ 
• Transformerを用いる 
• IOあたりを経由してliftする
DSLのテスト 
• 主に2つに分かれる 
• Primitivesのテスト 
• DSL上ロジックのテスト
Primitivesのテスト 
• Primitivesの性質を担保するテスト記述 
• 例 
• RESTful frameworkにおいてprimitives 
• post, get, put, delete 
• post >>= delete 
• 環境は前後で変化しない 
• getも環境変化無し 
• put == put >> put 
• idempotency
DSL上のテスト 
• 普通に書きましょう
Monad Instanceの差し替え 
による純粋テスト 
• DSLをMonadのサブクラスとして定義している 
• Monad Instanceの差し替えが可能 
• 任意のDSLは純粋にテストすることが出来る[要検証] 
• QuickCheckを用いてDSLロジックをランダムテスト 
可能 
• 環境を明示的に扱えば良い
DSLの合成 
• DSLはMonadのサブクラスとして定義してある 
• 複数のDSLを定義しておくことにより、DSLの合成は型 
クラスによるad-hock多相で容易に可能 
• class Monad m => DSL1 m where … 
• class Monad m => DSL2 m where … 
• baz :: (DSL1 m, DSL2 m) => m () 
• qux :: (DSL1 m, DSL2 m) => m ()
DSLの保守問題 
• 不安定なライブラリ/フレームワーク上で安全に開発を行う 
• DSL化を行った後、問題は大抵2つに分かれる 
• Primitivesに関する問題 
• DSL上のロジックの問題 
• 保守問題は、primitivesのみに関わる 
• Library/Frameworkの破壊的変更の影響範囲は、高々 
primitivesにのみ限定される
DSLの機能拡張問題 
• 機能拡張問題はprimitivesにまず関係する 
• Primitivesの追加は必要かどうか 
• 追加が必要な場合、以下は認識しておくべきだろう 
• 設計の為の追加か 
• 動的性質の為の追加か
他のエンジニアへの 
タスクアサイン 
• DSLで問題を区切ることにより、以下が期待出来る 
• エンジニアへのタスクアサインの単位が明確になる 
• エンジニアの能力に応じたアサインが可能になる 
• DSLが閉じている/限定されているためにレビューが容 
易になる
状態遷移問題 
• システムが状態により全く異なる挙動をとる場合、DSL 
のネストすることにより、全てのコードをDSL単位で混 
ざること無く明確に分離することが出来る。 
• 例えばベースMonadの上にTransformerを載せ、すぐに 
runすればよい。そのシステムは載せるTransformerに 
よって異なる挙動をとる。 
• 拡張が容易であり、状態が複雑になってもコードの複雑 
度は増えない
スクリプティング 
• Haskell上でスクリプティングにはMonadが最適 
• ゲームのNPCの挙動の記述 
• 複雑になりがちなスキルやバトルの記述
FFIとDSL 
• FFI関数を用いてprimitivesを構築した場合、 
• C言語の型付きDSLを構築出来る
DSLからの脱却 
• 何らかの事情でDSLを捨てなければならないことがある 
かもしれない 
• DSLとなる型クラスを捨て、多相化したパラメータを機 
械的に置換することにより、以下が残る 
• Primitivesだった関数 
• Primitivesを用いていた関数群
Advanced Topic 
• DSLの単相化 
• Free Monad 
• DSLのinstanceを含めた合成 
• Eff, Effin 
• 状態遷移の静的なチェック 
• Indexed Monad
まとめ 
• DSLを簡単に構築できることは、エンジニアとしては非 
常に価値がある

An engineer uses monads