上でリンクしたように、次のMonadReader
インスタンスがありFree
ます。
instance (Functor m, MonadReader e m) => MonadReader e (Free m) where
これが言っているm
のは、Functor
が であり、 のMonadReader e
インスタンスがある場合、 の内部でインスタンスm
を利用することもできるということです。ただし、これには、DSL Functor の場合のインスタンスが既に存在している必要があります。これは通常、DSL ファンクターで使用できる選択肢が大幅に制限されるため、通常は望ましくありません。MonadReader
Free
MonadReader
m
したがって、使用Free (ReaderT r DSL) a
する代わりに、逆にレイヤー化することをお勧めします。つまり 、ファンクターである必要があるReaderT r (Free DSL) a
という利点があります。DSL
これをより具体的にするために、DSL がどのように見えるかを述べていないことを踏まえて、Teletype
DSL の例を使用しましょう。
data TeletypeF a = GetChar (Char -> a) | PutChar Char a deriving Functor
type Teletype a = Free TeletypeF a
getChar :: Teletype Char
getChar = liftF (GetChar id)
putChar :: Char -> Teletype ()
putChar c = liftF (PutChar c ())
putStrLn :: String -> Teletype ()
putStrLn str = traverse putChar str >> putChar '\n'
runTeletype :: Teletype a -> IO a
runTeletype = foldFree go
where go (GetChar k) = k <$> IO.getChar
go (PutChar c k) = IO.putChar c >> return k
putStrLn
DSLプリミティブから派生したプログラムPutChar
です。IO
モナドを使ってプログラムを解釈することができます。ここで、ReaderT
モナド変換子を使用して、行末セパレータの選択を延期できるようにしputStrLn
ます。したがって、次のように進めます。
type TeletypeReader a = ReaderT Char (Free TeletypeF) a
getChar' :: TeletypeReader Char
getChar' = lift getChar
putChar' :: Char -> TeletypeReader ()
putChar' c = lift (putChar c)
putStrLn' :: String -> TeletypeReader ()
putStrLn' str = do
traverse_ putChar' str
sep <- ask
putChar' sep
runTeletypeReader :: Char -> TeletypeReader a -> IO a
runTeletypeReader sep = runTeletype . flip runReaderT sep
そして今、私たちはできる:
λ> runTeletypeReader '\n' (putStrLn' "Hello" >> putStrLn' "World")
Hello
World
λ> runTeletypeReader ':' (putStrLn' "Hello" >> putStrLn' "World")
Hello:World: