Extensible Eff Applicative
@halcat0x15a
今日話すこと
● 背景
○ Applicative
○ Free Applicative
● Free Applicativeの応用
○ Exception Applicative: エラー付きの計算
○ IO(Async) Applicative: 非同期の計算
● Extensible Eff Applicative: 拡張可能なFreeAp
● Eff Monadとの組み合わせ
Applicativeとは
FunctorとMonadの中間にある型クラス
(<*>) はコンテナの中の関数をコンテナの中の値に
適用するような関数
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Applicativeとは
(<*>) は複数のコンテナを合成できる
以下はEither Applicativeの例
e1 :: Either String Integer
e1 = (+) <$> (Right 1) <*> (Right 2)
e2 :: Either String Integer
e2 = (+) <$> (Right 1) <*> (Left "hoge")
Free Applicativeとは
Applicativeをデータ型として表したもの
data FreeAp f a where
Pure :: a -> FreeAp f a
ImpureAp :: f a -> FreeAp f (a -> b) -> FreeAp f b
class Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Free Applicativeとは
FreeApはApplicativeのインスタンス
instance Applicative (FreeAp f) where
pure = Pure
Pure f <*> y = fmap f y
ImpureAp x y <*> z = ImpureAp x (flip <$> y <*> z)
Free Applicativeとは
Free Monadのように様々なApplicativeを表現できる
以下はFreeApによるEither Applicativeの表現
newtype Exc e a = Exc e
success :: a -> FreeAp (Exc e) a
success a = Pure a
failure :: e -> FreeAp (Exc e) a
failure e = ImpureAp (Exc e) (Pure id)
Free Applicativeとは
e1, e2と同等の計算を記述できる
e3 :: FreeAp (Exc String) Integer
e3 = (+) <$> (success 1) <*> (success 2)
e4 :: FreeAp (Exc String) Integer
e4 = (+) <$> (success 1) <*> (failure "hoge")
なぜFree Applicativeか
基本的にApplicativeでできることはMonadでできる
applicativeStyle = f <$> ma <*> mb
monadicStyle = do
a <- ma
b <- mb
return $ f a b
なぜFree Applicativeか
しかしApplicativeにしかできないこともある
ここではFreer Monadとデータ構造の比較を行う
Freer Monadとは
Monadをデータ型として表したもの
data Freer f a where
Pure :: a -> Freer f a
Impure :: f a -> (a -> Freer f b) -> Freer f b
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
Freerとの比較
FreeApとFreerのデータ構造を比較する
e5 :: FreeAp (Exc String) Integer
e5 = ImpureAp (Exc "foo") $ ImpureAp (Exc "bar") $ Pure (+)
e6 :: Freer (Exc String) Integer
e6 = Impure (Exc "foo") $ a -> Impure (Exc "bar") $ b ->
Pure (a + b)
Freerとの比較
Applicativeの式は”foo”と”bar”両方を参照できる
Monadの式は”foo”だけ参照できる
“bar”の取得には継続を実行する必要がある
ImpureAp :: f a -> FreeAp f (a -> b) -> FreeAp f b
Impure :: f a -> (a -> Freer f b) -> Freer f b
Free Applicativeの応用
エラーの収集ができる
runExc :: FreeAp (Exc e) a -> Either [e] a
runExc (Pure a) = Right a
runExc (ImpureAp (Exc e) k) =
case runExc k of
Right _ -> Left [e]
Left es -> Left (e : es)
> runExc e5
Left ["foo","bar"]
Free Applicativeの応用
平行計算ができる
runAsync :: FreeAp IO a -> IO a
runAsync (Pure a) = pure a
runAsync (ImpureAp x k) =
do
a <- async x
k' <- runAsync k
x' <- wait a
return $ k' x'
Free Applicativeの応用
runAsyncは次のような関数で動作を確認できる
delay1s :: FreeAp IO ()
delay1s = ImpureAp x (Pure id)
where
x = do
putStrLn "start"
threadDelay 1000000
putStrLn "end"
Free Applicativeの応用
非同期で動作しているように見える
> runAsync $ delay1s *> delay1s
starstt
art
end
end
Extensible Eff Applicative
ここまででApplicativeの有用性を示した
ここから拡張可能なFree Applicativeについて考える
Extensible Eff Applicative
Extensible EffectsはFreer MonadにOpen Unionを加えたもの
Free ApplicativeにOpen Unionを加える →
Extensible Eff Applicative!
Extensible Eff Applicative
定義はFree Applicativeとそこまで変わらない
data EffAp r a where
Pure :: a -> EffAp r a
ImpureAp :: Union r a -> EffAp r (a -> b) -> EffAp r b
Extensible Eff Applicative
重要なのは他の作用を跨いだhandlerを書けるかどうか
runExc :: EffAp (Exc e : r) a -> EffAp r (Either [e] a)
runExc (Pure a) = Pure $ Right a
runExc (ImpureAp u k) =
case decomp u of
Right (Exc e) -> fmap f (runExc k) where
f (Right _) = Left [e]
f (Left es) = Left (e : es)
Left u -> ImpureAp u $ fmap f (runExc k) where
f e a = fmap (k -> k a) e
Extensible Eff Applicative
IOは終端で処理するしかない
runAsync :: EffAp '[IO] a -> IO a
runAsync (Pure a) = pure a
runAsync (ImpureAp u k) =
do
let Right x = decomp u
a <- async x
k' <- runAsync k
x' <- wait a
return $ k' x'
Extensible Eff Applicative
エラー付き計算と非同期計算を組み合わせることができる
handle :: EffAp '[Exc String, IO] a -> IO (Either [String] a)
handle = runAsync . runExc
e7 :: (Member (Exc String) r, Member IO r) => EffAp r Integer
e7 = (+) <$> (delay1s *> success 1) <*> (success 2 <* delay1s)
e8 :: (Member (Exc String) r, Member IO r) => EffAp r Integer
e8 = (+) <$> (delay1s *> failure "foo") <*> (failure "bar" <*
delay1s)
Extensible Eff Applicative
> handle e7 >>= print
start
start
enedn
d
Right 3
> handle e8 >>= print
starstt
art
enend
d
Left ["foo","bar"]
Extensible Eff Applicative
拡張可能なFree Applicativeを示すことができた
effect handlers
ここで一般化されたハンドラについて考える
以下はEffの例
handle_relay :: (a -> Eff r w) ->
(forall v. t v -> (v -> Eff r w) -> Eff r w) ->
Eff (t : r ) a -> Eff r w
handle_relay ret _ (Pure x) = ret x
handle_relay ret h (Impure u q) = case decomp u of
Right x -> h x k
Left u -> Impure u k
where k = handle_relay ret h . q
effect handlers
あまりイケてないがそれっぽいものは書ける
handle_relay :: Functor f =>
(forall a. a -> EffAp r (f a)) ->
(forall a b. t a -> EffAp r (f (a -> b)) -> EffAp r (f b)) ->
EffAp (t : r ) a -> EffAp r (f a)
handle_relay ret _ (Pure x) = ret x
handle_relay ret h (ImpureAp u q) = case decomp u of
Right x -> h x k
Left u -> ImpureAp u (fmap f k)
where
k = handle_relay ret h q
f x a = fmap (k -> k a) x
effect handlers
ReaderとWriterの例
runReader :: i -> EffAp (Reader i : r) a -> EffAp r a
runReader i = coerce . handle_relay
(Pure . Identity)
(Ask -> fmap ((Identity k) -> Identity $ k i))
runWriter :: Monoid w => EffAp (Writer w : r) a -> EffAp r (w, a)
runWriter = handle_relay
(a -> Pure (mempty, a))
((Tell v) -> fmap ((w, k) -> (mappend v w, k ())))
Effとの組み合わせ
Effと相互に利用するにはどうするか
少なくともMonadとApplicativeでprimitiveを分けたくない
Effとの組み合わせ(変換)
EffAp -> Effへの変換を提供する
toEff :: EffAp r a -> Eff.Eff r a
toEff (Pure a) = Eff.Pure a
toEff (ImpureAp u k) = Eff.Impure u (a -> fmap (k -> k a)
(toEff k))
Effとの組み合わせ(変換)
● Pros
○ Monadの操作とApplicativeの操作で型が分かれる
● Cons
○ モナドを利用したい時に変換しなければならない
Effとの組み合わせ(コンストラクタ)
コンストラクタに含める
data Eff r a where
Pure :: a -> Eff r a
Impure :: Union r a -> (a -> Eff r b) -> Eff r b
ImpureAp :: Union r a -> Eff r (a -> b) -> Eff r b
Effとの組み合わせ(コンストラクタ)
関数のところを抽象化してもよい
data Eff r a where
Pure :: a -> Eff r a
Impure :: Union r a -> Arr r a b -> Eff r b
data Arr r a b = ArrA (Eff r (a -> b)) | ArrM (a -> Eff r b)
Effとの組み合わせ(コンストラクタ)
● Pros
○ 使いやすい
● Cons
○ Applicativeの操作を型で保証できない
Effとの組み合わせ(型パラメータ)
型パラメータを増やす
data Eff h r a where
Pure :: a -> Eff h r a
Impure :: Union r a -> h (Eff h r) a b -> Eff h r b
data Apply f a b where
Apply :: f (a -> b) -> Apply f a b
data Bind f a b where
Bind :: (a -> f b) -> Bind f a b
Effとの組み合わせ(型パラメータ)
MonadとApplicativeで共通のprimitiveを使える
send :: (FromApply h, Member f r) => f a -> Eff h r a
send fa = Impure (inj fa) (fromApply $ Apply $ Pure id)
class FromApply h where
fromApply :: Functor f => Apply f a b -> h f a b
Effとの組み合わせ(型パラメータ)
● Pros
○ コンストラクタが共通
○ 型が分かれる
● Cons
○ 型が複雑になる
まとめ
● Applicativeにできて、Monadにできないことがある
● extensibleなFree Applicativeが作れる
● Eff MonadにFree Applicativeを組み込めるかもしれない
みなさんへの課題
● 簡潔なハンドラの定義
● データ構造をどうするか
参考文献
● http://okmij.org/ftp/Haskell/extensible/more.pdf
● https://www.slideshare.net/konn/freer-monads-more-exten
sible-effects-59411772
● https://arxiv.org/abs/1403.0749
● http://oleg.fi/gists/posts/2018-02-21-single-free.html
おわり

Extensible Eff Applicative