Pipesチュートリアルを
読んだ話

2015/11/7 関数型ストリーム処理勉強会
by ちゅーん(@its_out_of_tune)

自己紹介

  • 野生のHaskller(28♂)
  • 東京都近郊に生息(休職中)

  • クルージング(スケボー)
  • ボルダリング

  • スプラトゥーン
  • 当たらない方のリッター使い

HN: ちゅーん

Twitter:
 @its_out_of_tune
Github:
 tokiwoousaka

自己紹介

  • Takahashi Monad:
     スライド作成用の言語内DSL
     本スライドもこれで作成
  • Sarasvati:
     永遠に開発中のオーディオインターフェイス
     PortAudioのラッパーのラッパー
  • ブログ:http://tune.hateblo.jp/
     ポーカーゲームを作る連載やってます

近状

宣伝

来る11/23

宣伝

東京流通センター
第二展示場

宣伝

第二十一回
文学フリマ東京

宣伝

暗黒定数式 Vol2
寄稿してます

宣伝

❤(ӦvӦ。)よろしく

ここ数日間の活動

  • ~10/28:暗黒定数式の原稿
  • ~11/2:原稿見直しとか
  • ~11/3:イカ
  • ~11/4:イカ
  • ~11/5:Pipesを初めてインストール
  • ~11/6:資料作った

実は・・・

ストリーム処理
ライブラリとか
今まで使った事無い

そんなわけで

今日はゆるふわに行きます

Pipesとは

Pipes3つの特徴

  • Effects
  • Streaming
  • Composability

(´・ω・`)?

よーわからん

超簡単なサンプル

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

なるほどわからん

Pipesの型

Pipesの4つの型

基本的には全部、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制約は省略してます

runEffect関数

ProducerからConsumerまで繋げば、Effect型になるので
runEffect関数でアクションを実行する事ができる。

ghci> :t runEffect
runEffect :: Monad m => Effect m r -> m r

別にIOアクションじゃなくても良い

Pipes.Preludeの3関数

最初のサンプルに出てきた奴(実際にはちょっと違うケド)

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

Proxyをひも解く

Proxyの構造

Tutorialの次の図がわかりやすい

Proxy a' a b' b m r

Upstream | Downstream
+---------+
a' <== <== b' -- Information flowing upstream
| |
a ==> ==> b -- Information flowing downstream
+----|----+
v
r

Producerの構造

upstream側が閉じている。
尚、X = Void

type Producer b = Proxy X () () b

Upstream | Downstream
+---------+
X <== <== () -- 無視
| |
() ==> ==> b
+----|----+
v
r

Effectの構造

downstream側が閉じている。

type Consumer a = Proxy () a () X

Upstream | Downstream
+---------+
() <== <== () -- 無視
| |
a ==> ==> X
+----|----+
v
r

Pipesの構造

up/downstream双方向とも開いている

type Pipe a b = Proxy () a () b

Upstream | Downstream
+---------+
() <== <== () -- 無視
| |
a ==> ==> b
+----|----+
v
r

Effectの構造

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

こんな型あるよ1

upstreamからupstream、あるいは
downstreamからdownstreamの流れ。用途は良くわかっていない

type Server b' b = Proxy X () b' b
type Client a' a = Proxy a' a () X

こんな型あるよ2

各型の多相版、やっぱり用途は良くわからない。

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

yield関数とawait関数

ProducerやConsumerを作るのに使う基礎的な関数
Tutorialはまず、この説明から入ってるけど、どうなんだ。

yield :: Monad m => a -> Producer a m ()
await :: Monad m => Consumer a m a

まとめ

まとめ

  • 結局ストリーム処理良くわからない
  • でもPipesの使い方はそんな難しく無さそう
  • みた感じ綺麗な構造がチラホラ
  • 使えるようになったら便利っぽい気はする
  • もうちょっと頑張る

ありがとうございました
m(__)m