アズなんとかさん
職業C++er
日曜Haskeller
同人SS書き
所属/関係サークル
・RedTailCat
・暗黒定数式(Vol2はお休み)
@as_capabl (twitter)
https://github.com/as-capabl
https://hackage.haskell.org/user/HidenoriAzuma
Transducer:
pipes/conduit/machinesで
SourceとかSinkとかPipeをひっくるめた物を
便宜的にこう呼ぶ
pipes etcはTransducerを直列につないで何かするもの
例. 入力 >>> フィルタ >>> 出力
直列つなぎは射の合成
=>TransducerはCategory(圏)である!!!
直列つなぎ。得意。
並列つなぎ。
Categoryの枠外。
fanout(machines)などで何とか。
??????
あまり知られていないが、Categoryクラス(baseパッケージ)が存在する
pipes, conduit, machines: instance宣言はない (型シグネチャの関係で)
Arrow系(以降で紹介): 宣言している (Category a => Arrow aなので)
class Category cat where
id :: cat a a
(.) :: cat a b -> cat b c -> cat a c
pipesのspawnで
一応可能。
できる事はAkka streamやLINQ/Rxとほぼ同じ。
スレッドとTVarのコストが掛かります。
原論文
"Generalising Monads to Arrows"
John Hughes, 1998
http://www.cse.chalmers.se/~rjmh/Papers/arrows.pdf
CategoryとMonadの
間にある型クラス
注:MonadからArrow, Categoryを作るには
Kleisliを使用
Categoryのメンバ
・id
・(.), (<<<), (>>>)
Arrowのメンバ
・arr
・first
・second
・***
・&&&
ArrowLoopのメンバ
・loop
(オプション)
関数をアローに持ち上げる。
ストリームの機能としてはよくある。
右図の見方:○は純関数、□はアロー(Transducer)
class Arrow ar where
arr :: (a->b) -> ar a b
...
アローの横に「迂回路を作る」イメージ。
ストリームの機能としては、実を言うとかなり非自明。(後述)
class Arrow ar where
...
first :: ar a c -> ar (a, b) (c, b)
...
second sf =
arr swap >>>
first sf >>>
arr swap
sf1 *** sf2 =
first sf1 >>> second sf2
sf1 &&& sf2 =
arr (\x->(x,x)) >>>
(sf1 *** sf2)
proc (全体の入力) ->
do
(出力先) <- (アロー) -< (入力元)
ghc拡張。{-# LANGUAGE Arrows #-}で使用可能に。
パッと見た感じ……
ラムダ式の\がprocになったようなもの
の後ろに
モナドのdo文に謎の-<記号が入ったようなもの
例:普通の関数をアロー記法で定義
a->bもArrowなのでこれが可能
注:returnA == id == arr id
f :: Int -> Int
f = proc x ->
do
y <- arr (*2) -< x
returnA -< y
-- f 1 => 2
proc input ->
do
(a1, a2) <- a -< input
(b1, b2) <- b -< a2
rec
d1 <- d -< (b1, e1)
e1 <- e -< d1
(c1, c2) <- c -< (d1, b1)
f -< a1
g1 <- g -< c2
h -< g1
i -< c1
実は(見た目を除けば)違いは一点しかない
Arrow記法は『-< の左側に一時変数を含められない』
(手前のスライドでは省略しましたが、そういう制約があります)
ArrowApplyインスタンスがあると、
左に一時変数を入れる事も可能に
=>もうArrow記法の意味ないですね!?
f x = proc _ ->
do
r <- getLineA -< ()
putStrLnA (x ++ r) -<< ()
-- getLineA, putStrLnAは架空の関数(でも簡単に定義可能)
streamproc
-- ちょっと問題がある(後述)
netwire ほかFRPライブラリ
-- 本来の用途の外=>いらない所、足りない所が
machinecell (拙作)
-- ver3に向けて工事中
https://hackage.haskell.org/package/auto
2015年3月リリース
netwireに"インスパイア"された
ストリーム特化のライブラリ
リストにarrを適用
ghci> streamAuto (arr (*2)) [1..10]
[2,4,6,8,10,12,14,16,18,20]
scanl (+) 0 [1..10]と同じ
ghci> streamAuto (accum (+) 0) [1..10]
[1,3,6,10,15,21,28,36,45,55]
もちろん合成可能
ghci> streamAuto (arr (*2) >>> accum (+) 0) [1..10]
[2,6,12,20,30,42,56,72,90,110]
アロー記法でも
ghci> streamAuto
(proc x -> do {y <- arr (*2)-< x; accum (+) 0 -< y})
[1..10]
[2,6,12,20,30,42,56,72,90,110]
これでもOK
ghci> streamAuto
(proc x -> do {accum (+) 0 -< x * 2})
[1..10]
[2,6,12,20,30,42,56,72,90,110]
arrMで副作用
注:Arrowのメンバではなく、Auto独自の関数
ghci> streamAuto (arr show >>> arrM putStrLn) [1..10]
1
2
3
...
入力に使う場合
=>空の値を変換する
ghci> streamAuto
(arrM (\_ -> getLine) >>> arr reverse >>> arrM putStrLn)
(replicate 5 ())
foo
oof
...
シノニム: effect = arrM . const
ghci> streamAuto
(effect getLine >>> arr reverse >>> arrM putStrLn)
(replicate 5 ())
foo
oof
...
入出力は必ず 1-1 対応 -> filterとかできない
ので、Blip型を導入
・実装は単なるMaybe型
・1 - [0, 1]対応を実現,
3の倍数の時だけ出力
emitOn - 条件に合うときだけ値を持つBlipストリームを作る
arrMB - Blipが値を持たない時は実行しないarrM
ghci> import Control.Auto.Effects
ghci> streamAuto
(emitOn (\x -> x `mod` 3 == 0) >>> arrMB print)
[1..10]
3
6
9
fizzBuzz = proc x ->
do
let nowFizz = x `mod` 3 == 0
nowBuzz = x `mod` 5 == 0
fizzBuzz <- emitOn (==(True, True))
-< (nowFizz, nowBuzz)
fizz <- emitOn (==(True, False))
-< (nowFizz, nowBuzz)
buzz <- emitOn (==(False, True))
-< (nowFizz, nowBuzz)
showN <- emitOn (==(False, False))
-< (nowFizz, nowBuzz)
effectB $ putStrLn "FizzBuzz"
-< fizzBuzz
effectB $ putStrLn "Fizz" -< fizz
effectB $ putStrLn "Buzz" -< buzz
arrMB putStrLn -< show x <$ showN
4本の出力装置に振り分けるイメージ
注 (<$) = fmap . const
@Data.Functor FRP等でとてもよく出てくる
fizzBuzzCore = proc x ->
do
let nowFizz = x `mod` 3 == 0
nowBuzz = x `mod` 5 == 0
fizzBuzz <- emitOn (==(True, True))
-< (nowFizz, nowBuzz)
...
returnA -<
(fizzBuzz, fizz, buzz, x <$ showN)
main = streamAuto `flip` [0..100] $
proc x ->
do
(fizzBuzz, fizz, buzz, showNum) <-
fizzBuzzCore -< x
effectB $ putStrLn "FizzBuzz" -< fizzBuzz
effectB $ putStrLn "Fizz" -< fizz
...
コード分割
effectBの所を好きに変更可
(普通にやるとCallback hell)
Signal/Slotにちょっと似てる?
グラフはループさせることができる
=出力値を次の入力に使える
=局所的な状態の実現
fib = proc _ ->
do
rec x <- (delayN 2) 1 -< z
y <- (delayN 1) 1 -< z
let z = x + y
id -< x
-- streamAuto fib [1..5] => [1, 1, 2, 3, 5]
fib = proc _ ->
do
rec x <- (delayN 2) 1 -< z
y <- (delayN 1) 1 -< z
let z = x + y
id -< x
main =
do
Right fib0 <-
readAutoDef "save.dat" fib
let (r, fib') = stepAutoN' 5 fib0 ()
print r
writeAuto "save.dat" fib'
https://hackage.haskell.org/package/auto
(Readme, Haddockドキュメント)
http://blog.jle.im/entries/series/+all-about-auto
(ブログ記事)
autoは 1-[0, 1] 対応まで
pipes等は 1-*(一対多)
.. <- await
yield ..
yield ..
↑みたいなとき必然的に発生
=>コルーチンが実装可能であることの必要条件
例)
f :: Auto m () Int -- 1, 2を返す
g :: Auto m () Int -- 1, 2, 3を返す
このとき f&&&g :: Auto m () (Int, Int)は……
(1, 1), (2, 2), (???, 3)
https://hackage.haskell.org/package/streamproc
streamproc(原論文の実装)
勝手にコピーしたり切ったりする
https://groups.google.com/forum/#!topic/haskell-pipes/H6YdVhyNksk
pipesの試験実装
組み合わせを出力 (1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3)
どちらもアロー記法にしたとき非直感的な動きになる
https://hackage.haskell.org/package/machinecell
拙作
元々はmachinesのArrow化を目指したライブラリ
途中でYampa/netwireを参考に
=>autoとは従兄弟の関係
autoとの違い
In) await/yield
Out) Serialization
個数問題の解決法:全部Event(autoでいうBlip)型にする。
run :: ProcessA a (Event b) (Event c) -> a [b] [c]
全体の入力/出力がEvent
construct :: Plan i o -> ProcessA a (Event i) (Event o)
Planモナド = await/yieldを含むコルーチンモナド
=>oをyieldするとEvent oが出てくるイメージ
先の例)
f :: ProcessA a (Event ()) (Event Int) -- 1, 2を返す
g :: ProcessA a (Event ()) (Event Int) -- 1, 2, 3を返す
f&&&g :: ProcessA a (Event ()) (Event Int, Event Int)
となるので
いらない所にNoEvent(MaybeでいうNothing)を詰めれば解決
(Event 1, Event 1), (Event 2, Event 2), (NoEvent, Event 3)
Arrowでストリーム処理がしたい
-> Serializationが欲しい -> auto
-> await/yieldが欲しい -> machinecell
ご清聴ありがとうございました