Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.
並行プログラミングと
継続モナド
関数型言語交流会 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...
Próxima SlideShare
Cargando en…5
×

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

6.922 visualizaciones

Publicado el

:)

Publicado en: Tecnología
  • Sé el primero en comentar

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

  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

×