extensible/Effect
モナド変換子を定義する場合、mtlパッケージと協調するにはMonadStateやMonadIOのようなクラスのインスタンスを最低6つは定義しなければならず、また、新しいクラスを作る場合も、既存のモナド変換子に対してインスタンスを作る必要があり、労力は加速するばかりである。拡張可能系構造の共通の利点である「インスタンスを共通化できる」という効果がここで効いてくる。拡張可能エフェクトは、変換子の組み合わせでは表現できないような振る舞いを含むモナドを作る際に特に力を発揮する。 高速(freer-effectsの約2倍)
名前をつけることで、同じ型を持つ作用を複数持たせることができる
mtl パッケージと互換性がある
Actionにより、独自の作用を簡単に定義できる
基本形
モナドトランスフォーマーと同じように利用できる。
code: (hs)
{- stack repl
--resolver nightly-2018-05-14
--package extensible
--package mtl
-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} import Data.Extensible
import Data.Extensible.Effect.Default
import Control.Monad (replicateM_)
import Control.Monad.Reader (ask)
import Control.Monad.State (get, put)
import Control.Monad.Writer (tell)
import Data.Monoid (Sum (Sum))
type ExampleM = Eff
'[ ReaderDef Int
, StateDef Int
, WriterDef (Sum Int)
]
testEff :: ExampleM ()
testEff = replicateM_ 100 $ do
r <- ask
s <- get
tell (Sum s)
put $! s + r
runExtensible :: ExampleM a -> ((a, Int), Sum Int)
runExtensible = leaveEff
. runWriterDef
. flip runStateDef 0
. flip runReaderDef 1
main :: IO ()
main = print $ runExtensible testEff
実行結果
code: (hs)
*Main> main
(((),100),Sum {getSum = 4950})
名前付き Eff
例えば、モナドトランスフォーマーでは State や Writer など同じエフェクトを2つ重ねることはできない。
Extensible エフェクトでは、名前付き Eff を利用することでこれを実現する。
code: (hs)
{- stack repl
--resolver nightly-2018-05-14
--package extensible
-}
{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE TypeApplications #-} {-# OPTIONS_GHC -fno-warn-simplifiable-class-constraints #-} import Data.Extensible
import Data.Extensible.Effect.Default
type FooBarM xs =
( Associate "foo" (WriterEff String) xs
, Associate "bar" (WriterEff String) xs
)
test :: FooBarM xs => Eff xs ()
test = do
main :: IO ()
main = do
print $ leaveEff $ runWriterEff @ "foo" $ runWriterEff @ "bar" test
print $ leaveEff $ runWriterEff @ "bar" $ runWriterEff @ "foo" test
実行結果
code: (hs)
*Main> main
(((),"hogefuga"),"Hello world")
(((),"Hello world"),"hogefuga")
mtl パッケージとの互換性
code: (hs)
{- stack repl
--resolver nightly-2018-05-14
--package extensible
--package mtl
-}
{-# LANGUAGE DataKinds #-} import Data.Extensible
import Data.Extensible.Effect.Default
import Control.Monad.State (MonadState, modify)
increment :: (Num a, MonadState a m) => m ()
increment = modify (+1)
main :: IO ()
main = print $ leaveEff $ runStateDef increment 0
実行結果
code: (hs)
*Main> main
((),1)
mtl との対応関係
table:table
デフォルト名 カスタマイズ
eff Eff '[ReaderDef r, WriterDef w, StateDef s] Eff '["foo" >: WriterEff String, "bar" >: WriterEff String]
mtl ReaderT r (WriterT w (State s)) WriterT String (Writer String)
実行するには runReaderDef, runWriterDef, runStateDef で、leaveEff で締めくくる。
code: (hs)
leaveEff . runStateDef . runWriterDef . runReaderDef
アクションの持ち上げと分解
関連する記録