Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.
並行プログラミングと
継続モナド
関数型言語交流会 2015-09-13
@ruicc
だれ
• @ruicc
• サーバサイドエンジニア
• Haskeller
言いたいこと
• 継続モナドが新しいモジュラリティを与えてくれる
• 並行プログラミングで継続モナド便利
3
言いたいこと(裏)
• みんな並行プログラミングしようぜ!
• 知見がもっと欲しい
4
プロローグ
並列並行Haskell本
• Simon Marlow著
• 素晴らしい本なので
• とりあえず読みましょう
• 以降heyhey Haskell本と呼ぶ
12章:並行ネットワークサーバ
• 単純でスケーラブルなチャットサーバ実装
• telnetでアクセスして、部屋へ入り、チャットする
7
とりあえず実装/改良してみた
• 局所的に問題を解いていくコード
• すごい参考になる
• IO版、Cont版作って単純ベンチマーク
• VM RAM3GB
• 9000clientsさばく
• エラーなし
• profile見たらほとんど文字列...
例: main周辺
9
main :: IO ()
main = withSocketsDo $ do
server <- newServer
sock <- listenOn (PortNumber (fromIntegral port))
...
main :: IO ()
main = withSocketsDo $ do
server <- newServer
sock <- listenOn (PortNumber (fromIntegral port))
forever $ do...
main :: IO ()
main = withSocketsDo $ do
server <- newServer
sock <- listenOn (PortNumber (fromIntegral port))
forever $ do...
main :: IO ()
main = withSocketsDo $ do
server <- newServer
sock <- listenOn (PortNumber (fromIntegral port))
forever $ do...
main :: IO ()
main = withSocketsDo $ do
server <- newServer
sock <- listenOn (PortNumber (fromIntegral port))
forever $ do...
main :: IO ()
main = withSocketsDo $ do
server <- newServer
sock <- listenOn (PortNumber (fromIntegral port))
forever $ do...
並行プログラミング
並列性と並行性
• 並列性
• 計算をより速くするために資源(CPUコア等)
を用いる
• 基本的に決定的計算(純粋計算)
• 並行性
• 複数のスレッドを用いてプログラムを構築する
• 非決定的(各スレッド上でIOが発生)
16
なぜ並行プログラミングか
• プログラムの構造がシンプルになる場合がある
• スレッド単位で処理を構築し、それらを組み合わせ
るというモジュラリティを提供する (heyhey
Haskell本より)
17
並行プログラミングは
設計の問題である
並行プログラミングの難しさ
• スレッド単位で構築し、それらを組み合わせる
• スレッド毎の処理は単純にかける
• 組み合わせる箇所が難しい
19
今日はスレッドの組み
合わせの話ではない
並行プログラミングの難しさ
• スレッド同士を組み合わせるのが難しい?
• 共有メモリの操作を安全に合成出来る
• STMモナド(モナドが重要)
21
並列並行Haskell本を読もう!
• 並行モデル
• 共有メモリモデル
• トランザクションモデル
(STM)
• アクターモデル
• 例外
• 詳しく書いてある
今日はさらに細かいコードの
モジュラリティの話
Chatサーバを実装してみて
• すべてのコードがほぼ一直線の数珠繋ぎになっている
ことに気づいた
• どこを切り出してもその後の処理が全て付いてくる
• これでは再利用やテストがしにくい、なぜそうなって
いる?
24
なぜコードが一直線なのか?
• サンプル用のコードだから
• 問題を局所に押し込めて解くスタイル
• 非同期例外の存在
25
「問題を局所に押し込める」
• 例えば後始末が必要なリソースの扱い
• もし後始末がコード内に散らばってしまうと…
• コードが読みづらい/把握が難しい
• エンバグしやすい
• 保守がつらい
• 拡張しづらい
26
例外を用いる
• 何かする時に後始末も同時に書いてしまう
• 以降後始末は考えなくても良い
27
例外例:チャットルームへ入る
28
readName = do
hPutStrLn handle "What is your name?"
name <- hGetLine handle
if null name
then readName
e...
readName = do
hPutStrLn handle "What is your name?"
name <- hGetLine handle
if null name
then readName
else mask $ restore...
例外例:チャットルームへ入る
30
readName = do
hPutStrLn handle "What is your name?"
name <- hGetLine handle
if null name
then readName
e...
readName = do
hPutStrLn handle "What is your name?"
name <- hGetLine handle
if null name
then readName
else mask $ restore...
readName = do
hPutStrLn handle "What is your name?"
name <- hGetLine handle
if null name
then readName
else mask $ restore...
例外で得たもの、失ったもの
• 例外を用いると、局所に問題を閉じ込め、安全にプ
ログラムを書ける
• 閉じ込められるかどうかは問題による
• 例外を用いると、コードの構造が大きく制限される
33
例外でどう制限されるか?
• 例外を使ったら
• もぐるしかなくなる
• 例外スコープが必要なくなるまで
34
例外構文による制限(余談)
• Haskellでは例外機構は関数で提供されている
• 例外が構文になっている場合、厄介に思われるかも
しれない
• 関数が第1級ならbracketを用意すると便利
bracket :: IO a -> (a ->...
そもそも例外機構は必要か?
• 一般に必要かどうかは難しい問題
• 型システムで代替できないか?
• 無理
• 型システム外から飛んでくる例外が存在する
• つまり静的には捉えられないモノの存在
36
非同期例外(GHC)
• 型システムで捉えられない例外の一つ
• よってIO上で捕まえるしかない
• スレッドの外から飛んでくる例外
• ユーザの投げるシグナル
• メモリ不足等によって発生する例外
• スレッドを外から殺すための例外
• タイ...
非同期例外の存在(GHC)
• 常に例外が飛んでくる可能性がある
• 非同期例外を受け取らないスコープの必要性
(mask)
• 例外補足の必要性
• 先と同様にコードが制限される
38
例外の制限の回避は?
• 例外は(少なくともGHCでは)使わないといけないこ
とがわかった
• 例外を用いるとその後に実行することがプログラム
(関数)内に直に埋め込まれてしまう
• どうする?
39
高階関数を使う
• 関数型言語(!!)なので高階関数が使える
• その後にすることを引数で渡す
40
readName = do
hPutStrLn handle "What is your name?"
name <- hGetLine handle
if null name
then readName
else mask $ restore...
readName' cont = do
hPutStrLn handle "What is your name?"
name <- hGetLine handle
if null name
then readName
else mask $ r...
高階関数化によって
• 例外使うたびに似たような特殊な高階関数がたくさ
ん出来る
• うまく扱う方法はないか?
43
readName' :: (Server -> Client -> IO ()) -> IO ()
そこで継続モナドですよ
継続モナド
モナド?
モナドとは(Haskell)
• 「モナド則を満たすもの」
47
モナド則とは(Haskell)
• Monad mとそのメソッド(>>=), returnに対して以
下が成立すること
1. return x >>= f ≡ f x
2. m >>= return ≡ m
3. (m >>= f) >>= g...
モナドとは(Haskell)
• さっきのモナド則を満たす任意のものはモナド
49
モナド則の実用上の意味
• returnは何もしないアクション(モナド則1,2)
• (>>=)は二つのアクションを組み合わせる
• アクションの組み合わせ方は結合的(モナド則3)
50
整数の積の結合則
(X * Y) * Z == X * ...
モナド則の嬉しさ
• IOアクション3つ(act1, act2, act3)を考える
• act1 :: IO A
• act2 :: A -> IO B
• act3 :: B -> IO C
• これらの組み合わせは2通りの構造が考えられる...
モナド則の嬉しさ(2)
• IOアクションn個(act1, act2, ... ,actn)を考える
• act1 :: IO A
• act2 :: A -> IO B
...
• actn :: X -> IO Y
• これらの組み合わせは...
モナド則の嬉しさ(3)
• 複数の異なる構造を同一視して良い
➡ 構造の複雑さが軽減される
53
モナド則の嬉しさ(補足)
• パフォーマンス(動的性能)が同じとは言ってない
• 一般に、同じ結果になるプログラムが複数通りあっ
たらどれかが速い
54
代数的性質の嬉しさ
• みんな沢山知ってる代数的性質
• 交換則
• X * Y == Y * X
• 分配則
• X * (Y + Z) == X * Y + X * Z
• 結合則
• (X * Y) * Z == X * (Y * Z)
...
そして圏論へ
• 数学史に現れてきた代数的構
造をいろいろ包含する概念圏
を扱う
• プログラムの複雑さと戦おう
• 9/9発売
そして圏論へ
• 数学史に現れてきた代数的構
造をいろいろ包含する概念圏
を扱う
• プログラムの複雑さと戦おう
• 9/19発売
継続モナド
継続モナドのアクション
newtype Cont r a = Cont { runCont :: (a -> r) -> r }
type Cont' r a = (a -> r) -> r
• 関数を受け取って結果を返す関数、というアクション...
継続?
継続とは
• 「その後に実行すること」
61
62
newtype Cont r a = Cont { runCont :: (a -> r) -> r }
• 関数を受け取って結果を返す関数、というアクション
• 引数の関数はアクションの最後で実行される
継続モナドのアクション(再)
継...
継続モナドの定義
instance Monad (ContT r m) where
return x = ContT ($ x)
m >>= k =
ContT $ c -> runContT m (x -> runContT (k x) c)...
継続モナドの定義
instance Monad (ContT r m) where
return x = ContT ($ x)
m >>= k =
ContT $ c -> runContT m (x -> runContT (k x) c)...
継続モナドの定義
instance Monad (ContT r m) where
return x = ContT ($ x)
m >>= k =
ContT $ c -> runContT m (x -> runContT (k x) c)...
継続モナドの定義
instance Monad (ContT r m) where
return x = ContT ($ x)
m >>= k =
ContT $ c -> runContT m (x -> runContT (k x) c)...
継続モナド例(trivial)
action1, action2, action3 :: Cont r Int
action1 = return 42
action2 = return 13
action3 = return 2
cont_ex...
継続モナド例(trivial)
action1, action2, action3 :: Cont r Int
action1 = return 42
action2 = return 13
action3 = return 2
cont_ex...
69
action1, action2, action3 :: Cont r Int
action1 = return 42
action2 = return 13
action3 = return 2
cont_ex = do
x <- ac...
70
action1, action2, action3 :: Cont r Int
action1 = return 42
action2 = return 13
action3 = return 2
cont_ex = do
x <- ac...
71
cont_ex = do
x <- action1
y <- action2
z <- action3
return (x + y + z)
main = do
print $ runCont cont_ex id
継続モナド例(triv...
つまりどういうこと?
• 継続モナドを使うと
• アクションが並べた順に実行される
72
IOと何が違うのか?
• CPS(Continuation Passing Style)
• スタイルが違う
• できることは同じ
73
継続モナドの動的性能
• 同じことができるプログラムは、一般にどちらかが
速い
• 継続モナドやCPSを使うと速くなることがある
• 継続モナドはクロージャを大量に生成する
• GC頻度が上がるかも
74
継続モナドの他の特徴
はどうなのか?
継続モナドと例外
例外の補足
bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
77
例外の補足
bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
78
リソース取得
リソース解放
(必ず実行される) アクション
bracketとは
• 関数化された例外機構
• 例外構文を持たないだけで、同様の特徴を持つ
• コード構造が限定される
• とはいえそれでも便利
79
例外の補足
bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
80
継続っぽい!
簡単な例
81
bracket_demo :: ContT r IO Int
bracket_demo = do
fh <- ContT $ bracket
(openFile "tmp" WriteMode)
hClose
n <- Cont...
簡単な例
82
bracket_demo :: ContT r IO Int
bracket_demo = do
fh <- ContT $ bracket
(openFile "tmp" WriteMode)
hClose
n <- Cont...
簡単な例
83
bracket_demo :: ContT r IO Int
bracket_demo = do
fh <- ContT $ bracket
(openFile "tmp" WriteMode)
hClose
n <- Cont...
簡単な例
84
bracket_demo :: ContT r IO Int
bracket_demo = do
fh <- ContT $ bracket
(openFile "tmp" WriteMode)
hClose
n <- Cont...
簡単な例
85
bracket_demo :: ContT r IO Int
bracket_demo = do
fh <- ContT $ bracket
(openFile "tmp" WriteMode)
hClose
n <- Cont...
さてどう動くか?
86
bracket_demo :: ContT r IO Int
bracket_demo = do
fh <- ContT $ bracket
(openFile "tmp" WriteMode)
hClose
n <- ...
動作
87
bracket_demo :: ContT r IO Int
bracket_demo = do
fh <- ContT $ bracket
(openFile "tmp" WriteMode)
hClose
n <- ContT ...
動作
88
bracket_demo :: ContT r IO Int
bracket_demo = do
fh <- ContT $ bracket
(openFile "tmp" WriteMode)
hClose
n <- ContT ...
動作
89
bracket_demo :: ContT r IO Int
bracket_demo = do
fh <- ContT $ bracket
(openFile "tmp" WriteMode)
hClose
n <- ContT ...
どういうこと?
• 継続モナド上(ContT r IO a)では例外はアクションの
逆順で伝播する
90
継続モナドのアクション
newtype ContT r m a =
ContT { runContT :: (a -> m r) -> m r }
•継続はアクションの最後に実行される
•継続内で例外が発生したら、アクションに戻ってくる
•つま...
継続モナドと例外
• 継続モナドによって例外機構の制約から抜けること
が出来た
• モナドなのでアクションが組み合わせやすい
92
継続モナドを考える
• 継続モナドは新しいモジュラリティを提供する
93
関数呼び出しイメージ
94
f
g
hcall
call
return
return
関数呼び出しイメージ
95
f
g
hcall
call
return
return
使いまわせる粒度
継続モナドイメージ
96
f
g
h
tail call
tail call
継続モナドイメージ
97
f
g
h
tail call
tail call
使いまわせる粒度
使いまわせる粒度
使いまわせる粒度
継続モナドと例外イメージ
98
f
g
h
call
call
finalizer
finalizer
継続モナドと例外イメージ
99
f
g
h
call
call
finalizer
finalizer
使いまわせる粒度
使いまわせる粒度
使いまわせる粒度
まとめ
• Haskellでは並行プログラミングに例外は必須
• 例外を使うとコード構造に制約ができる
• 継続モナドで例外が頻発するコードでもモジュラリ
ティを高く保つことができる
100
エピローグ
101
-- sketch
launchServer :: Port -> IO ()
launchServer port = (`runContT` return) $ do
client <- ContT $ acceptLoo...
エピローグ
102
-- Add logger
launchServer :: Port -> IO ()
launchServer port = (`runContT` return) $ do
logger <- newLogger
cli...
Nächste SlideShare
Wird geladen in …5
×

並行プログラミングと継続モナド

8.977 Aufrufe

Veröffentlicht am

:)

Veröffentlicht in: Technologie
  • Loggen Sie sich ein, um Kommentare anzuzeigen.

並行プログラミングと継続モナド

  1. 1. 並行プログラミングと 継続モナド 関数型言語交流会 2015-09-13 @ruicc
  2. 2. だれ • @ruicc • サーバサイドエンジニア • Haskeller
  3. 3. 言いたいこと • 継続モナドが新しいモジュラリティを与えてくれる • 並行プログラミングで継続モナド便利 3
  4. 4. 言いたいこと(裏) • みんな並行プログラミングしようぜ! • 知見がもっと欲しい 4
  5. 5. プロローグ
  6. 6. 並列並行Haskell本 • Simon Marlow著 • 素晴らしい本なので • とりあえず読みましょう • 以降heyhey Haskell本と呼ぶ
  7. 7. 12章:並行ネットワークサーバ • 単純でスケーラブルなチャットサーバ実装 • telnetでアクセスして、部屋へ入り、チャットする 7
  8. 8. とりあえず実装/改良してみた • 局所的に問題を解いていくコード • すごい参考になる • IO版、Cont版作って単純ベンチマーク • VM RAM3GB • 9000clientsさばく • エラーなし • profile見たらほとんど文字列でメモリ消費してた • OOMKillerにやられた 8
  9. 9. 例: main周辺 9 main :: IO () main = withSocketsDo $ do server <- newServer sock <- listenOn (PortNumber (fromIntegral port)) forever $ do (handle, host, port) <- accept sock forkFinally (talk handle server) (_ -> hClose handle)
  10. 10. main :: IO () main = withSocketsDo $ do server <- newServer sock <- listenOn (PortNumber (fromIntegral port)) forever $ do (handle, host, port) <- accept sock forkFinally (talk handle server) (_ -> hClose handle) Portのlisten 例: main周辺 10
  11. 11. main :: IO () main = withSocketsDo $ do server <- newServer sock <- listenOn (PortNumber (fromIntegral port)) forever $ do (handle, host, port) <- accept sock forkFinally (talk handle server) (_ -> hClose handle) Portのlisten accept 例: main周辺 11
  12. 12. main :: IO () main = withSocketsDo $ do server <- newServer sock <- listenOn (PortNumber (fromIntegral port)) forever $ do (handle, host, port) <- accept sock forkFinally (talk handle server) (_ -> hClose handle) Portのlisten accept accept毎にfork 例: main周辺 12
  13. 13. main :: IO () main = withSocketsDo $ do server <- newServer sock <- listenOn (PortNumber (fromIntegral port)) forever $ do (handle, host, port) <- accept sock forkFinally (talk handle server) (_ -> hClose handle) Portのlisten accept accept毎にfork その後やること 例: main周辺 13
  14. 14. main :: IO () main = withSocketsDo $ do server <- newServer sock <- listenOn (PortNumber (fromIntegral port)) forever $ do (handle, host, port) <- accept sock forkFinally (talk handle server) (_ -> hClose handle) 問題点 14 その後やることが直に埋め込まれている その後やること
  15. 15. 並行プログラミング
  16. 16. 並列性と並行性 • 並列性 • 計算をより速くするために資源(CPUコア等) を用いる • 基本的に決定的計算(純粋計算) • 並行性 • 複数のスレッドを用いてプログラムを構築する • 非決定的(各スレッド上でIOが発生) 16
  17. 17. なぜ並行プログラミングか • プログラムの構造がシンプルになる場合がある • スレッド単位で処理を構築し、それらを組み合わせ るというモジュラリティを提供する (heyhey Haskell本より) 17
  18. 18. 並行プログラミングは 設計の問題である
  19. 19. 並行プログラミングの難しさ • スレッド単位で構築し、それらを組み合わせる • スレッド毎の処理は単純にかける • 組み合わせる箇所が難しい 19
  20. 20. 今日はスレッドの組み 合わせの話ではない
  21. 21. 並行プログラミングの難しさ • スレッド同士を組み合わせるのが難しい? • 共有メモリの操作を安全に合成出来る • STMモナド(モナドが重要) 21
  22. 22. 並列並行Haskell本を読もう! • 並行モデル • 共有メモリモデル • トランザクションモデル (STM) • アクターモデル • 例外 • 詳しく書いてある
  23. 23. 今日はさらに細かいコードの モジュラリティの話
  24. 24. Chatサーバを実装してみて • すべてのコードがほぼ一直線の数珠繋ぎになっている ことに気づいた • どこを切り出してもその後の処理が全て付いてくる • これでは再利用やテストがしにくい、なぜそうなって いる? 24
  25. 25. なぜコードが一直線なのか? • サンプル用のコードだから • 問題を局所に押し込めて解くスタイル • 非同期例外の存在 25
  26. 26. 「問題を局所に押し込める」 • 例えば後始末が必要なリソースの扱い • もし後始末がコード内に散らばってしまうと… • コードが読みづらい/把握が難しい • エンバグしやすい • 保守がつらい • 拡張しづらい 26
  27. 27. 例外を用いる • 何かする時に後始末も同時に書いてしまう • 以降後始末は考えなくても良い 27
  28. 28. 例外例:チャットルームへ入る 28 readName = do hPutStrLn handle "What is your name?" name <- hGetLine handle if null name then readName else mask $ restore -> do -- <1> ok <- checkAddClient server name handle case ok of Nothing -> restore $ do -- <2> hPrintf handle "The name %s is in use, Choose anothern" name readName Just client -> restore (runClient server client) -- <3> `finally` removeClient server name
  29. 29. readName = do hPutStrLn handle "What is your name?" name <- hGetLine handle if null name then readName else mask $ restore -> do -- <1> ok <- checkAddClient server name handle case ok of Nothing -> restore $ do -- <2> hPrintf handle "The name %s is in use, Choose anothern" name readName Just client -> restore (runClient server client) -- <3> `finally` removeClient server name チャットルームへ入る 例外例:チャットルームへ入る 29
  30. 30. 例外例:チャットルームへ入る 30 readName = do hPutStrLn handle "What is your name?" name <- hGetLine handle if null name then readName else mask $ restore -> do -- <1> ok <- checkAddClient server name handle case ok of Nothing -> restore $ do -- <2> hPrintf handle "The name %s is in use, Choose anothern" name readName Just client -> restore (runClient server client) -- <3> `finally` removeClient server name 例外処理
  31. 31. readName = do hPutStrLn handle "What is your name?" name <- hGetLine handle if null name then readName else mask $ restore -> do -- <1> ok <- checkAddClient server name handle case ok of Nothing -> restore $ do -- <2> hPrintf handle "The name %s is in use, Choose anothern" name readName Just client -> restore (runClient server client) -- <3> `finally` removeClient server name 入った後の処理 例外例:チャットルームへ入る 31
  32. 32. readName = do hPutStrLn handle "What is your name?" name <- hGetLine handle if null name then readName else mask $ restore -> do -- <1> ok <- checkAddClient server name handle case ok of Nothing -> restore $ do -- <2> hPrintf handle "The name %s is in use, Choose anothern" name readName Just client -> restore (runClient server client) -- <3> `finally` removeClient server name その後の処理が 全て付いてくる 例外例:チャットルームへ入る 32
  33. 33. 例外で得たもの、失ったもの • 例外を用いると、局所に問題を閉じ込め、安全にプ ログラムを書ける • 閉じ込められるかどうかは問題による • 例外を用いると、コードの構造が大きく制限される 33
  34. 34. 例外でどう制限されるか? • 例外を使ったら • もぐるしかなくなる • 例外スコープが必要なくなるまで 34
  35. 35. 例外構文による制限(余談) • Haskellでは例外機構は関数で提供されている • 例外が構文になっている場合、厄介に思われるかも しれない • 関数が第1級ならbracketを用意すると便利 bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c 35
  36. 36. そもそも例外機構は必要か? • 一般に必要かどうかは難しい問題 • 型システムで代替できないか? • 無理 • 型システム外から飛んでくる例外が存在する • つまり静的には捉えられないモノの存在 36
  37. 37. 非同期例外(GHC) • 型システムで捉えられない例外の一つ • よってIO上で捕まえるしかない • スレッドの外から飛んでくる例外 • ユーザの投げるシグナル • メモリ不足等によって発生する例外 • スレッドを外から殺すための例外 • タイムアウト実装に利用する 37
  38. 38. 非同期例外の存在(GHC) • 常に例外が飛んでくる可能性がある • 非同期例外を受け取らないスコープの必要性 (mask) • 例外補足の必要性 • 先と同様にコードが制限される 38
  39. 39. 例外の制限の回避は? • 例外は(少なくともGHCでは)使わないといけないこ とがわかった • 例外を用いるとその後に実行することがプログラム (関数)内に直に埋め込まれてしまう • どうする? 39
  40. 40. 高階関数を使う • 関数型言語(!!)なので高階関数が使える • その後にすることを引数で渡す 40
  41. 41. readName = do hPutStrLn handle "What is your name?" name <- hGetLine handle if null name then readName else mask $ restore -> do -- <1> ok <- checkAddClient server name handle case ok of Nothing -> restore $ do -- <2> hPrintf handle "The name %s is in use, Choose anothern" name readName Just client -> restore (runClient server client) -- <3> `finally` removeClient server name その後の処理 その後の処理の高階関数化 41
  42. 42. readName' cont = do hPutStrLn handle "What is your name?" name <- hGetLine handle if null name then readName else mask $ restore -> do -- <1> ok <- checkAddClient server name handle case ok of Nothing -> restore $ do -- <2> hPrintf handle "The name %s is in use, Choose anothern" name readName Just client -> restore (cont server client) -- <3> `finally` removeClient server name その後の処理 その後の処理の高階関数化 42 引数で渡す
  43. 43. 高階関数化によって • 例外使うたびに似たような特殊な高階関数がたくさ ん出来る • うまく扱う方法はないか? 43 readName' :: (Server -> Client -> IO ()) -> IO ()
  44. 44. そこで継続モナドですよ
  45. 45. 継続モナド
  46. 46. モナド?
  47. 47. モナドとは(Haskell) • 「モナド則を満たすもの」 47
  48. 48. モナド則とは(Haskell) • Monad mとそのメソッド(>>=), returnに対して以 下が成立すること 1. return x >>= f ≡ f x 2. m >>= return ≡ m 3. (m >>= f) >>= g ≡ m >>= (x -> f x >>= g) 48
  49. 49. モナドとは(Haskell) • さっきのモナド則を満たす任意のものはモナド 49
  50. 50. モナド則の実用上の意味 • returnは何もしないアクション(モナド則1,2) • (>>=)は二つのアクションを組み合わせる • アクションの組み合わせ方は結合的(モナド則3) 50 整数の積の結合則 (X * Y) * Z == X * (Y * Z)
  51. 51. モナド則の嬉しさ • IOアクション3つ(act1, act2, act3)を考える • act1 :: IO A • act2 :: A -> IO B • act3 :: B -> IO C • これらの組み合わせは2通りの構造が考えられる • (act1 >>= act2) >>= act3 • act1 >>= (b -> act2 b >>= act3) • モナド則3より、これら2構造は同一のものとして扱っ て良い 51
  52. 52. モナド則の嬉しさ(2) • IOアクションn個(act1, act2, ... ,actn)を考える • act1 :: IO A • act2 :: A -> IO B ... • actn :: X -> IO Y • これらの組み合わせはX通りの構造が考えられる • (...(act1 >>= act2) >>= ... >>= actn) • モナド則3より、これらX個の構造は同一のものとして 扱って良い 52
  53. 53. モナド則の嬉しさ(3) • 複数の異なる構造を同一視して良い ➡ 構造の複雑さが軽減される 53
  54. 54. モナド則の嬉しさ(補足) • パフォーマンス(動的性能)が同じとは言ってない • 一般に、同じ結果になるプログラムが複数通りあっ たらどれかが速い 54
  55. 55. 代数的性質の嬉しさ • みんな沢山知ってる代数的性質 • 交換則 • X * Y == Y * X • 分配則 • X * (Y + Z) == X * Y + X * Z • 結合則 • (X * Y) * Z == X * (Y * Z) • 上記はどれも複数の構造を同一視して良い性質 • 複雑さと戦うための武器の一つ 55
  56. 56. そして圏論へ • 数学史に現れてきた代数的構 造をいろいろ包含する概念圏 を扱う • プログラムの複雑さと戦おう • 9/9発売
  57. 57. そして圏論へ • 数学史に現れてきた代数的構 造をいろいろ包含する概念圏 を扱う • プログラムの複雑さと戦おう • 9/19発売
  58. 58. 継続モナド
  59. 59. 継続モナドのアクション newtype Cont r a = Cont { runCont :: (a -> r) -> r } type Cont' r a = (a -> r) -> r • 関数を受け取って結果を返す関数、というアクション • 引数の関数はアクションの最後で実行される 59
  60. 60. 継続?
  61. 61. 継続とは • 「その後に実行すること」 61
  62. 62. 62 newtype Cont r a = Cont { runCont :: (a -> r) -> r } • 関数を受け取って結果を返す関数、というアクション • 引数の関数はアクションの最後で実行される 継続モナドのアクション(再) 継続 継続 • 継続 =「その後に実行すること」が表現されている 継続
  63. 63. 継続モナドの定義 instance Monad (ContT r m) where return x = ContT ($ x) m >>= k = ContT $ c -> runContT m (x -> runContT (k x) c) 63
  64. 64. 継続モナドの定義 instance Monad (ContT r m) where return x = ContT ($ x) m >>= k = ContT $ c -> runContT m (x -> runContT (k x) c) 64 mを走らせる mに渡す継続
  65. 65. 継続モナドの定義 instance Monad (ContT r m) where return x = ContT ($ x) m >>= k = ContT $ c -> runContT m (x -> runContT (k x) c) 65 (k x)を走らせる (k x)に渡す継続xはmが渡す
  66. 66. 継続モナドの定義 instance Monad (ContT r m) where return x = ContT ($ x) m >>= k = ContT $ c -> runContT m (x -> runContT (k x) c) 66 全体の結果はアクションを返す 継続cは外からもらう (k x)に渡す継続
  67. 67. 継続モナド例(trivial) action1, action2, action3 :: Cont r Int action1 = return 42 action2 = return 13 action3 = return 2 cont_ex = do x <- action1 y <- action2 z <- action3 return (x + y + z) 67 action1の継続は どれか?
  68. 68. 継続モナド例(trivial) action1, action2, action3 :: Cont r Int action1 = return 42 action2 = return 13 action3 = return 2 cont_ex = do x <- action1 y <- action2 z <- action3 return (x + y + z) 68 action1の継続 (action1後に実行)
  69. 69. 69 action1, action2, action3 :: Cont r Int action1 = return 42 action2 = return 13 action3 = return 2 cont_ex = do x <- action1 y <- action2 z <- action3 return (x + y + z) 継続モナド例(trivial) action2の継続 (action2後に実行)
  70. 70. 70 action1, action2, action3 :: Cont r Int action1 = return 42 action2 = return 13 action3 = return 2 cont_ex = do x <- action1 y <- action2 z <- action3 return (x + y + z) 継続モナド例(trivial) action3の継続 (action3後に実行)
  71. 71. 71 cont_ex = do x <- action1 y <- action2 z <- action3 return (x + y + z) main = do print $ runCont cont_ex id 継続モナド例(trivial) 最後に実行される 継続
  72. 72. つまりどういうこと? • 継続モナドを使うと • アクションが並べた順に実行される 72
  73. 73. IOと何が違うのか? • CPS(Continuation Passing Style) • スタイルが違う • できることは同じ 73
  74. 74. 継続モナドの動的性能 • 同じことができるプログラムは、一般にどちらかが 速い • 継続モナドやCPSを使うと速くなることがある • 継続モナドはクロージャを大量に生成する • GC頻度が上がるかも 74
  75. 75. 継続モナドの他の特徴 はどうなのか?
  76. 76. 継続モナドと例外
  77. 77. 例外の補足 bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c 77
  78. 78. 例外の補足 bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c 78 リソース取得 リソース解放 (必ず実行される) アクション
  79. 79. bracketとは • 関数化された例外機構 • 例外構文を持たないだけで、同様の特徴を持つ • コード構造が限定される • とはいえそれでも便利 79
  80. 80. 例外の補足 bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c 80 継続っぽい!
  81. 81. 簡単な例 81 bracket_demo :: ContT r IO Int bracket_demo = do fh <- ContT $ bracket (openFile "tmp" WriteMode) hClose n <- ContT $ bracket (hPutStrLn fh "Gain 42" >> return 42) (n -> hPutStrLn fh $ "Finalize: " ++ show n) l <- ContT $ bracket (hPutStrLn fh "Gain 13" >> return 13) (n -> hPutStrLn fh $ "Finalize: " ++ show n) liftIO $ throwIO (ErrorCall "heyhey") return $ n + l
  82. 82. 簡単な例 82 bracket_demo :: ContT r IO Int bracket_demo = do fh <- ContT $ bracket (openFile "tmp" WriteMode) hClose n <- ContT $ bracket (hPutStrLn fh "Gain 42" >> return 42) (n -> hPutStrLn fh $ "Finalize: " ++ show n) l <- ContT $ bracket (hPutStrLn fh "Gain 13" >> return 13) (n -> hPutStrLn fh $ "Finalize: " ++ show n) liftIO $ throwIO (ErrorCall "heyhey") return $ n + l ContT r IO a
  83. 83. 簡単な例 83 bracket_demo :: ContT r IO Int bracket_demo = do fh <- ContT $ bracket (openFile "tmp" WriteMode) hClose n <- ContT $ bracket (hPutStrLn fh "Gain 42" >> return 42) (n -> hPutStrLn fh $ "Finalize: " ++ show n) l <- ContT $ bracket (hPutStrLn fh "Gain 13" >> return 13) (n -> hPutStrLn fh $ "Finalize: " ++ show n) liftIO $ throwIO (ErrorCall "heyhey") return $ n + l bracketを 継続モナドアクションに
  84. 84. 簡単な例 84 bracket_demo :: ContT r IO Int bracket_demo = do fh <- ContT $ bracket (openFile "tmp" WriteMode) hClose n <- ContT $ bracket (hPutStrLn fh "Gain 42" >> return 42) (n -> hPutStrLn fh $ "Finalize: " ++ show n) l <- ContT $ bracket (hPutStrLn fh "Gain 13" >> return 13) (n -> hPutStrLn fh $ "Finalize: " ++ show n) liftIO $ throwIO (ErrorCall "heyhey") return $ n + l 例外機構を (ネストではなく) 縦に並べている
  85. 85. 簡単な例 85 bracket_demo :: ContT r IO Int bracket_demo = do fh <- ContT $ bracket (openFile "tmp" WriteMode) hClose n <- ContT $ bracket (hPutStrLn fh "Gain 42" >> return 42) (n -> hPutStrLn fh $ "Finalize: " ++ show n) l <- ContT $ bracket (hPutStrLn fh "Gain 13" >> return 13) (n -> hPutStrLn fh $ "Finalize: " ++ show n) liftIO $ throwIO (ErrorCall "heyhey") return $ n + l わざと例外を投げる
  86. 86. さてどう動くか? 86 bracket_demo :: ContT r IO Int bracket_demo = do fh <- ContT $ bracket (openFile "tmp" WriteMode) hClose n <- ContT $ bracket (hPutStrLn fh "Gain 42" >> return 42) (n -> hPutStrLn fh $ "Finalize: " ++ show n) l <- ContT $ bracket (hPutStrLn fh "Gain 13" >> return 13) (n -> hPutStrLn fh $ "Finalize: " ++ show n) liftIO $ throwIO (ErrorCall "heyhey") return $ n + l
  87. 87. 動作 87 bracket_demo :: ContT r IO Int bracket_demo = do fh <- ContT $ bracket (openFile "tmp" WriteMode) hClose n <- ContT $ bracket (hPutStrLn fh "Gain 42" >> return 42) (n -> hPutStrLn fh $ "Finalize: " ++ show n) l <- ContT $ bracket (hPutStrLn fh "Gain 13" >> return 13) (n -> hPutStrLn fh $ "Finalize: " ++ show n) liftIO $ throwIO (ErrorCall "heyhey") return $ n + l 順々にリソース取得が 実行される
  88. 88. 動作 88 bracket_demo :: ContT r IO Int bracket_demo = do fh <- ContT $ bracket (openFile "tmp" WriteMode) hClose n <- ContT $ bracket (hPutStrLn fh "Gain 42" >> return 42) (n -> hPutStrLn fh $ "Finalize: " ++ show n) l <- ContT $ bracket (hPutStrLn fh "Gain 13" >> return 13) (n -> hPutStrLn fh $ "Finalize: " ++ show n) liftIO $ throwIO (ErrorCall "heyhey") return $ n + l 例外が投げられる
  89. 89. 動作 89 bracket_demo :: ContT r IO Int bracket_demo = do fh <- ContT $ bracket (openFile "tmp" WriteMode) hClose n <- ContT $ bracket (hPutStrLn fh "Gain 42" >> return 42) (n -> hPutStrLn fh $ "Finalize: " ++ show n) l <- ContT $ bracket (hPutStrLn fh "Gain 13" >> return 13) (n -> hPutStrLn fh $ "Finalize: " ++ show n) liftIO $ throwIO (ErrorCall "heyhey") return $ n + l 逆順でfinalizerが 実行される
  90. 90. どういうこと? • 継続モナド上(ContT r IO a)では例外はアクションの 逆順で伝播する 90
  91. 91. 継続モナドのアクション newtype ContT r m a = ContT { runContT :: (a -> m r) -> m r } •継続はアクションの最後に実行される •継続内で例外が発生したら、アクションに戻ってくる •つまり、例外はアクションを逆順に伝播する 91
  92. 92. 継続モナドと例外 • 継続モナドによって例外機構の制約から抜けること が出来た • モナドなのでアクションが組み合わせやすい 92
  93. 93. 継続モナドを考える • 継続モナドは新しいモジュラリティを提供する 93
  94. 94. 関数呼び出しイメージ 94 f g hcall call return return
  95. 95. 関数呼び出しイメージ 95 f g hcall call return return 使いまわせる粒度
  96. 96. 継続モナドイメージ 96 f g h tail call tail call
  97. 97. 継続モナドイメージ 97 f g h tail call tail call 使いまわせる粒度 使いまわせる粒度 使いまわせる粒度
  98. 98. 継続モナドと例外イメージ 98 f g h call call finalizer finalizer
  99. 99. 継続モナドと例外イメージ 99 f g h call call finalizer finalizer 使いまわせる粒度 使いまわせる粒度 使いまわせる粒度
  100. 100. まとめ • Haskellでは並行プログラミングに例外は必須 • 例外を使うとコード構造に制約ができる • 継続モナドで例外が頻発するコードでもモジュラリ ティを高く保つことができる 100
  101. 101. エピローグ 101 -- sketch launchServer :: Port -> IO () launchServer port = (`runContT` return) $ do client <- ContT $ acceptLoop port loginedClient <- ContT $ login client roomId <- ContT $ joinRoom loginedClient ContT $ chat loginedClient roomId
  102. 102. エピローグ 102 -- Add logger launchServer :: Port -> IO () launchServer port = (`runContT` return) $ do logger <- newLogger client <- ContT $ acceptLoop port loginedClient <- ContT $ login client roomId <- ContT $ joinRoom loginedClient ContT $ chat loginedClient roomId logger

×