Engineer-uses-monads
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

Engineer-uses-monads

on

  • 337 views

函数型なんたらの集い 2014 in Tokyo

函数型なんたらの集い 2014 in Tokyo
http://connpass.com/event/8634/

Statistics

Views

Total Views
337
Views on SlideShare
165
Embed Views
172

Actions

Likes
3
Downloads
4
Comments
0

2 Embeds 172

https://twitter.com 156
http://connpass.com 16

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Engineer-uses-monads Presentation Transcript

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