Monad tutorial
-
Upload
hideyuki-tanaka -
Category
Technology
-
view
13.338 -
download
5
description
Transcript of Monad tutorial
函数プログラミングの集い2011 チュートリゕル
「モナドについて」
株式会社 Preffered Infrastructure
田中 英行
自己紹介
• 田中英行 (@tanakh, id:tanakh)
• 株式会社 Preferred Infrastructure (PFI) 勤務 –検索エンジンのゕルゴリズムとか作ってます
• Haskell (2004~)
• C++ (1998~)
• BASIC (1992~)
• プログラミングコンテスト愛好家 – ICPC, ICFPC, CodeJam, TopCoder, …
本日の概要
• モナドとは?
• モナドの使い方
–モナドのセマンテゖクス
–典型的なモナドの例
– Haskellでのモナド
• モナドの作り方
–モナドのンスタンスに
–モナド変換子
モナドとは? What is Monad?
モナドとは何か?
• 非常に難しい質問です
• 既に数多くの人がそれに答えようとしています
–そして各々が一見全く違う主張をしている
幾つかの例
『モナドは象である』
『モナドはメタフゔーではない』
『プログラマブル・コンテナ』
『コンベゕのゕナロジー』
from “All About Monads”
いろいろありますが…
• 結局モナドとはなんなのか?
モナドの力の秘密、 いつか解き明かしてみたいな…
モナドについての疑問
• 疑問にもいろいろある
–モナドとは何か?
–何の役に立つのか?
–なぜモナドなのか? モナドがもてはやされるのか?
• ゕローとモナド、どうして差がついた…
• それぞれについて、私なりの見解
モナドとは何なのか?
• モナドとはパラダムである !
抽象的な話
パラダム?
• プログラミングパラダム
–手続き型
–関数型
–論理型
–オブジェクト志向
–モナデゖック(?)
• ここじゃない
モナドとは手続き型パラダムの再定義である
• プログラミングパラダム
–手続き型
• モナデゖック
–関数型
–論理型
–オブジェクト志向
意味的側面からの モナドのメタフゔー
• モナドは『プログラマブルセミコロン』である
Programmable Semicolon
• Real World Haskell より
手続き型言語と構造化定理
• プログラムは、「順次・反復・分岐」の基本的な構造の組み合わせによって記述できる
• これらはモナドへ直接的にマッピングできる
それで、何が嬉しいの?
• 分岐のセマンテゖクス書き換え
• 反復のセマンテゖクス書き換え
–そういうことができる言語は過去にはあった
• 逐次のセマンテゖクス書き換え
–かつて無いもの(…の様な気がします)!
逐次のセマンテゖクス
• およそほとんどの手続き型言語では、 セミコロンの意味は変えられない
–空気のような存在
• セミコロンの意味を変えることの意義が 伝統的に見逃されて来たのではないか?
int main() { foo(); bar(); }
main = foo >> bar
継続との関係
• モナドは継続(continuation)の一般化とも考えることができます
• 継続=各セミコロンにおけるプログラムの状態
• モナドはセミコロンを記述するわけなので、その状態を取り出すことは簡単
–実際に継続モナドというものがあります
セミコロンをいじることにより 可能になること
• 普通のプログラムを非決定計算に変える
• 普通のプログラムにエラーハンドリングを(プログラムを書き換えずに)追加する
• 普通のプログラムに暗黙の状態を導入する
コンテクスト
• 具体的なモナドに対して、それが計算に付加価値を与えます。それを(計算の)コンテクストと呼ぶことにします –つまり、モナドというのはコンテクスト付きの計算ということができる
• 例えば… – monad:リストモナド → ctx:非決定性
– monad:Stateモナド → ctx:mutableな状態
– etc …
それぞれにそういうプログラムを 書けばいいんじゃないんですか?
• コンテクストごとに異なる記法
• コンテクストごとに異なるプログラム
• コンテクストごとに異なる語彙
・・・
抽象化の欠如
コンテクストの抽象化
• 共通のゕルゴリズムの記述
• 共通のコード片の抽象化
よりメタレベルの抽象化へ
床下配線のゕナロジー
• モナドによるセミコロンの抽象化は 床下配線と例えられることも
–見えないところを書き換える
少し具体的な話
例
• N-クーン問題
– Int が与えられて解を返す
–解とはなんぞや?
• 全解列挙
• どれか一つを見つける
• 一番いい解を頼む
–いろいろ考えうる
N-Queen問題
• モナデゖックに書くと、これらを統一的に扱える
–解の列挙のストラテジをモナドとして記述
–問題を解くゕルゴリズムからの分離
前半まとめ
• モナドとは何なのか? –計算コンテクストの抽象化である
• なんでそれが嬉しいの? –具体的なコンテクストに依存しないコードを書ける
–抽象化したものを具体的なコードにできる
• なぜモナドなの? –モナドは構造化定理に必要な要素を自然に記述できて、なおかつ簡潔であるから
三行で言うと
• モナドは関数レベルで
• メタプログラミング
• するためのものです
(´・_・`)えっ…?
モナド入門・モナドの使い方 How to use Monads
実装レベルのお話
モナドとは
• “ある特定の方法”で組み合わせることのできる計算のことをまとめて、
と呼びます。
モナド
ちなみに
• 組み合わせ可能な計算はモナドだけではありません – いろいろな抽象化におけるそれが存在
• 例えば、 – 関数 (関数合成)
– フゔンクタ (関手)
– ゕプリカテゖブ・フゔンクタ
– ゕロー
– Iteratee
– etc…
ひとまず置いておいて、 Haskellでのモナドのお話
Haskellでのモナド
• 次のような型クラス
これのンスタンスが具体的なモナド=計算コンテクスト
モナドになっている標準データ型
• 標準データ型の中にもモナドがある
–リスト
– Maybe
– Either
– IO
モナド則の必要性
• モナド則は、モナドを安全に組み合わせるのに必要
–これらの挙動が同じでなければ、組み合わせ方によって意味が変わるということになる
Haskellのdo記法
• Haskellではモナドを非常によく使うので、専用の構文糖衣が用意されています
高度なモナドの使い方
リストモナド
• リストはモナドにできる
• リストは非決定 計算のコンテクスト と捉えることが できる
Maybeモナド
• Maybeもモナド
• 失敗するかもしれない計算
IOモナド
• 入出力を行う可能性のある計算
• HaskellではIOモナドを介してしか入出力を扱えない
IOモナドの功罪
• モナドに関するよくある誤解
–モナドってpurely functional languageでIOするためにあるんでしょ?
• モナドはIOのためにあるのではありません
• モナドはIOのためにあるのではありません
–大事なことなので
モナド則
• すべてのモナドは次のモナド則を満たす ”べき”である
–満たす保証をするのはプログラマの責任
–あえて満たさなくても良い
モナド変換子(Monad Transformers)
• モナドとモナドを組み合わせるもの
– (cf. 計算と計算を組み合わせるものがモナド)
動機
• 複数の計算コンテクストを合成したい
–失敗するかもしれないIO計算
–エラーハンドリングできるパーザー
– etc, …
MTL (Monad Transformer Library)
• 標準のモナドラブラリ
– Preludeのモナドを大幅強化
• これらのものを含む
–幾つかの標準的なモナド
–これらのモナドを合成するための モナド変換子(Monad Transformers)
MTLに含まれるモナド
• Monad.Cont (継続)
• Monad.Reader (ReadOnly状態)
• Monad.Writer (ログ出力)
• Monad.State (Mutable状態)
• Monad.List (非決定計算)
• Monad.Error (エラーハンドリング)
• これに加え、それぞれのモナド変換子版
モナド変換子
• 2つのモナドを合成するためのもの
• 例えばStateモナドの場合:
– StateT s m a • モナド変換子版Stateモナド
• State s a と比べて、mというパラメータが追加
• mに合成したいモナドを代入
– StateT s IO a • IOモナドを内包したStateモナド
• IOモナドの操作とStateモナドの操作が両方できる
持ち上げ(lift)
• StateT s IO の例
– StateT s IO の中でIOを行うには、持ち上げ(lift)を行う必要がある(型が合わないので)
モナドクラス
• 例えばIOを行うだけの計算
• これは、StateT s IO 以外のモナドでも使えて欲しい
– ErrorT err IO
– ReaderT s IO
– …
MonadIOクラス
• そのために、IOをliftできるクラス全体を抽象化したMonadIOクラスを定義
• 先のコードは次のような型に
• StateT s IO などをMonadIOのンスタンスにすれば使用可能に。
一般の持ち上げ
• モナド変換子に渡されるモナドはIOだけではない –ネストする場合もある
• 一般ケースのために、MonadTransというクラスが用意されている
–内側のモナド外側のモナドに持ち上げることができる
中盤まとめ
• モナドとは >>= と return の2つの演算が定義されたもの
• 標準データ型の多くのものがモナドになっている
• mtlというモナド変換子ラブラリがある
• モナド変換子を用いてモナドを組み合わせる
• モナド持ち上げで型の異なるモナドを張り合わせる
Advanced Topics
monad-control (1)
• liftの逆をするもの
• 動機
– Haskellの例外ハンドリング機構はIOモナドベース
–渡せるものがIO固定
• MonadIO に対しても例外ハンドルしたい
monad-control (2)
• MonadIOに対して一般化
• それを行えるようにするために、MonadControlIOというクラスを用意
モナドいろいろ
• 近年実に様々なラブラリがモナデゖックラブラリとして提供されるようになりました
• それらの一部を紹介していきたいと思います
MonadPar
• 並列計算を記述するためのモナド
– `par`, `seq` などをモナド化したもの
Parser
• パーザいろいろ
– Parsec, Attoparsec, trifecta
WebApp
• WAI, Yesod, Snap, CGI, …
Interpreter
• hint, BASIC, …
モナドの作り方 How to design your monads
モナドを設計するにあたって
• 自分のラブラリが、モナドとして抽象化できることに気づいたとします
• しかし、モナドの作り方を間違うと、非常に使い勝手の悪いものができてしまいます –使い勝手のよいモナドの実装には気をつけるべきことが沢山あります
• ここまで紹介したことはすべて理解しておくことが望ましいです
1) 既存のモナドが利用できないか
• 実際のところ、mtlに含まれるモナドで、ほとんどのケースはカバーされます
• 作りたい計算が、mtlにあるモナドの組み合わせで実現できないかまず検討するべきです
2) モナド変換子版を用意する
• いざモナドを作るとなったら、(原理的に)可能なのであれば、モナド変換子版を必ず用意しましょう
–モナド変換子にはHogeTと、末尾に大文字Tをつけるのが慣習です
• 非モナド変換子版は、モナド変換子版にIdentityモナドを代入したものにします
–実装を重複させてはいけません
MonadIOクラスのンスタンスにする
• あなたのモナドをMonadIOのンスタンスにしておくのはとても良いことです
• あなたのモナドを利用するすべての場所でIOを行うことができるようになります
• MonadIOを用意しておくのはモナドによってIOを行うHaskellにとっては極めて重要なことです
※ IOモナドについて(1)
• IOモナドは、Haskellのプログラムの中では外すことのできないモナドです – unsafePerformIO などを除いて
• その結果、IOを呼ぶコードはそれ自身がIOを行わなくても、IOモナドにする必要があります
• Haskellによくある批判として、まともなプログラムを書いているとほとんどすべての関数の型がIOになる、というのがあります – IOモナドは感染するとか言われます
※ IOモナドについて(2)
• そこでMonadIOの出番です
• IOが必要なコンテクストについてのみ、MonadIOを要求させておけば良くなります
• Pureなモナドに関しては、具体的な型が決定するに従って、自動で持ち上げられることになります
• つまり、PureなコードとIOのコードのオーバーロードが可能になるということです
3) MonadControlIOの ンスタンスにする
• あなたのモナドが例外を正しく扱えるようにするために、MonadControlIOのンスタンスにしましょう
–これがないと bracket などが正しく後処理できません
4) MonadTransのンスタンスにする
• あなたのモナド変換子が、他のモナドを自動多段持ち上げ可能になるように(可能であれば)必ず、MonadTransのンスタンスにしましょう
5) Functor, Applicative のンスタンスにする
• Applicativeスタルというものがあります – http://d.hatena.ne.jp/kazu-yamamoto/20101211/1292021817
– などを参照
• あなたのモナドをFunctor, Applicativeのンスタンスにすると、使い勝手が大幅に向上します
• 必ずこれらのンスタンスにしましょう
– Monadのンスタンスは必ずFunctor及びApplicativeのンスタンスにできます
6) 必要に応じて、その他
• その他のモナドクラスのンスタンスにします
–エラーハンドリングを付けたいなら、
MonadError
–失敗に対する代替を与えたいときには、
Alternative
※ Alternativeクラスについて
• m1 <|> m2 なる演算子が定義されている
• m1が失敗したとき、m2の結果
• Alternativeの任意のンスタンスに対して – many p – pを失敗するまで繰り返し
– some p – pを失敗するまで1回以上繰り返し
– optional p – p が失敗したらNothing成功したらJust aを返す
–これらを定義することができる
GenericNewtypeDeriving
• 自分のモナドをこれらすべてのンスタンスにするのは骨の折れる作業です
• モナドがnewtypeの時、これをderiving で済ませることができます
– -XGenericNewtypeDeriving 言語拡張
後半まとめ
• モナドを作るにあたって
– MTLのモナドの組み合わせで実現できないか考える
–いろいろなクラスのンスタンスにしておく
–モナド以外の有用なクラスが標準にあるのでそれのンスタンスにもする