2015/11/7 関数型ストリーム処理勉強会
by ちゅーん(@its_out_of_tune)
HN: ちゅーん
Twitter:
@its_out_of_tune
Github:
tokiwoousaka
echo(三回)
import Pipes
import qualified Pipes.Prelude as P
main :: IO ()
main = do
runEffect $ P.stdinLn >-> P.take 3 >-> P.stdoutLn
Haskellなんだから
型を見れば良いんちゃう?
GHCiで確認
ghci> :t (>->)
(>->)
:: Monad m => Proxy a' a () b m r ->
Proxy () b c' c m r -> Proxy a' a c' c m r
なるほどわからん
基本的には全部、Proxyを元にtype宣言
Proxyの詳細は後述
type Effect m r = Proxy X () () X m r
type Producer b m r = Proxy X () () b m r
type Consumer a m r = Proxy () a () X m r
type Pipe a b m r = Proxy () a () b m r
Producer >-> Pipes >-> Consumer のように繋ぐ
各型引数の役割について推測してみよう
infixl 7 >->
(>->) :: Producer b m r -> Consumer b m r -> Effect m r
(>->) :: Producer b m r -> Pipe b c m r -> Producer c m r
(>->) :: Pipe a b m r -> Consumer b m r -> Consumer a m r
(>->) :: pipe a b m r -> pipe b c m r -> Pipe a c m r
-- ※画面に収まらない都合上Monad制約は省略してます
ProducerからConsumerまで繋げば、Effect型になるので
runEffect関数でアクションを実行する事ができる。
ghci> :t runEffect
runEffect :: Monad m => Effect m r -> m r
別にIOアクションじゃなくても良い
最初のサンプルに出てきた奴(実際にはちょっと違うケド)
P.stdinLn :: MonadIO m => Producer String m ()
P.stdoutLn :: MonadIO m => Consumer String m ()
P.take :: Monad m => Int -> Pipe a a m ()
EffectになっているのでrunEffect出来る
main :: IO ()
main = do
runEffect $ P.stdinLn >-> P.take 3 >-> P.stdoutLn
^ ^ ^
| | |
Producer Pipes Consumer
Tutorialの次の図がわかりやすい
Proxy a' a b' b m r
Upstream | Downstream
+---------+
a' <== <== b' -- Information flowing upstream
| |
a ==> ==> b -- Information flowing downstream
+----|----+
v
r
upstream側が閉じている。
尚、X = Void
type Producer b = Proxy X () () b
Upstream | Downstream
+---------+
X <== <== () -- 無視
| |
() ==> ==> b
+----|----+
v
r
downstream側が閉じている。
type Consumer a = Proxy () a () X
Upstream | Downstream
+---------+
() <== <== () -- 無視
| |
a ==> ==> X
+----|----+
v
r
up/downstream双方向とも開いている
type Pipe a b = Proxy () a () b
Upstream | Downstream
+---------+
() <== <== () -- 無視
| |
a ==> ==> b
+----|----+
v
r
ProducerからConsumerへの流れが完成している。
type Effect = Proxy X () () X
Upstream | Downstream
+---------+
X <== <== () -- やっぱ無視
| |
() ==> ==> X
+----|----+
v
r
(>->)演算子で繋ぐとこんな感じ
Producer Pipe Consumer
+-----------+ +----------+ +------------+
| | | | | |
X <== <== () <== <== () <== <== ()
| stdinLn | | take 3 | | stdoutLn |
() ==> ==> String ==> ==> String ==> ==> X
| | | | | | | | |
+-----|-----+ +----|-----+ +------|-----+
v v v
() () ()
結果、こうなる
Effect
+-----------------------------------+
| |
X <== <== () -- 無視じゃ
| stdinLn >-> take 3 >-> stdoutLn |
() ==> ==> X
| |
+----------------|------------------+
v
()
モノとしては単なるモナドっぽい。
中身の掘り下げは時間が足りなくて出来なかった。
data Proxy a' a b' b m r
= Request a' (a -> Proxy a' a b' b m r )
| Respond b (b' -> Proxy a' a b' b m r )
| M (m (Proxy a' a b' b m r))
| Pure r
instance Monad m => Monad (Proxy a' a b' b m) where
わりと基本っぽい。Proxyの返却値 r を、
次の処理に繋ぐ演算子なのだが、十分にいじれなかった。
(>~) :: Effect m b -> Consumer b m c -> Effect m c
(>~) :: Consumer a m b -> Consumer b m c -> Consumer a m c
(>~) :: Producer y m b -> Pipe b y m c -> Producer y m c
(>~) :: Pipe a y m b -> Pipe b y m c -> Pipe a y m c
こんな感じの図になるらしい。
+-------+ +-------+ +----------+
| | | | | |
a ==> f ==> y .-> b ==> g ==> y = a ==> f >~ g ==> y
| | | / | | | | | |
+---|---+ / +---|---+ +----|-----+
v / v v
b ---' c c
upstreamからupstream、あるいは
downstreamからdownstreamの流れ。用途は良くわかっていない
type Server b' b = Proxy X () b' b
type Client a' a = Proxy a' a () X
各型の多相版、やっぱり用途は良くわからない。
type Effect' m r = ∀x' x y' y . Proxy x' x y' y m r
type Producer' b m r = ∀x' x . Proxy x' x () b m r
type Consumer' a m r = ∀ y' y . Proxy () a y' y m r
type Server' b' b m r = ∀x' x . Proxy x' x b' b m r
type Client' a' a m r = ∀ y' y . Proxy a' a y' y m r
ProducerやConsumerを作るのに使う基礎的な関数
Tutorialはまず、この説明から入ってるけど、どうなんだ。
yield :: Monad m => a -> Producer a m ()
await :: Monad m => Consumer a m a