如何将函数DSL转换为Haskell中的Monad? [英] How do I convert a functional DSL into a Monad in Haskell?
问题描述
以下Haskell代码是一个简单的console IODSL:
data Ap a = Ap {runAp :: ApStep a}
数据ApStep a =
ApRead(字符串 - > Ap a)
| ApReturn a
| ApWrite String(Ap a)
ioRead k = Ap $ ApRead k
ioReturn a = Ap $ Ap返回a
ioWrite sk = Ap $ ApWrite sk
ioWriteLn s = ioWrite(s ++\\\
)
apTest =
ioWriteLnHello world! $
ioRead $ \i - >
ioWriteLn(You written [++ i ++])
ioReturn 10
uiRun ap =
case runAp ap of
ApRead k - > uiRun(k一些输入)
ApReturn a - >返回一个
ApWrite s k - > putStr s>> uiRun k
run = uiRun apTest
可以,不过我想使用monad
而不是使用$来编写应用程序apTest。换句话说就是这样:
apTest = do
ioWriteLnHello world!
i < - ioRead
ioWriteLn $您写过[++ ++++++]
return 10
问题在于代码阻止了所有将函数风格DSL变为monad的尝试。所以问题是如何为这个DSL实现monad实例,它允许你写apTest monad风格而不是$风格?
肯定这是一个单子。实际上,将它表达为一个免费的monad会更简单一些,但我们可以使用你所得到的。
下面是我们知道它是一个monad的方式:如果你有一个类型数据Foo a = ...
其中 Foo
表示某种递归树结构,其中 a
s只发生在树叶上,那么你就有了monad。 return a
是给我一棵由 a
标记的一片叶子组成的树, >> =
是在叶子处进行替换。
您的情况 有两种内部节点 Ap
是一个树结构,其中
ApReturn a
是一片叶子
ApRead
是一个节点,它没有标签,并且每个类型为 String
ApWrite
是一个由字符串
标记的节点,并且只有一个后裔离开它
我在下面的代码中添加了monad实例。 return
只是 AppReturn
(加上 Ap
包装器)。 >> =
只是递归地应用>> =
并代替叶子。
未来的几点提示
- 将类型签名放在顶层的所有东西上。您的同事,Stack Overflow评论者以及您未来的自我感谢。
-
Ap
wrapper是不必要的。请考虑删除它。
享受!
data Ap a = Ap {runAp :: ApStep a}
data ApStep a =
ApRead(String - > Ap a)
| ApReturn a
| ApWrite String(Ap a)
ioRead k = Ap $ ApRead k
ioReturn a = Ap $ Ap返回a
ioWrite sk = Ap $ ApWrite sk
ioWriteLn s = ioWrite(s ++\\\
)
apTest =
ioWriteLnHello world! $
ioRead $ \i - >
ioWriteLn(You written [++ i ++])
ioReturn 10
uiRun ap =
case runAp ap of
ApRead k - > uiRun(k一些输入)
ApReturn a - >返回一个
ApWrite s k - > putStr s>> uiRun k
run = uiRun apTest
实例Monad Ap其中
return = Ap。 ApReturn
Ap(ApReturn a)> = f = fa
Ap(ApRead r)> = f = Ap(ApRead(\\ ss→rs>> = f))
Ap(ApWrite sa)>> = f = Ap(ApWrite s(a> = f))
monadRead = Ap(ApRead - > return s))
monadWrite s = Ap(ApWrite s(return()))
monadWriteLn = monadWrite。 (++\\\
)
apTestMonad = do
monadWriteLnHello world!
i < - monadRead
monadWriteLn $您写了[++ ++++++++++]]
return 10
monadRun = uiRun apTestMonad
[1] http://www.haskellforall.com/2012/06/you-could-have-invented-free-monads.html
The following Haskell code is a simple "console IO" DSL:
data Ap a = Ap { runAp :: ApStep a }
data ApStep a =
ApRead (String -> Ap a)
| ApReturn a
| ApWrite String (Ap a)
ioRead k = Ap $ ApRead k
ioReturn a = Ap $ ApReturn a
ioWrite s k = Ap $ ApWrite s k
ioWriteLn s = ioWrite (s ++ "\n")
apTest =
ioWriteLn "Hello world!" $
ioRead $ \i ->
ioWriteLn ("You wrote [" ++ i ++ "]") $
ioReturn 10
uiRun ap =
case runAp ap of
ApRead k -> uiRun (k "Some input")
ApReturn a -> return a
ApWrite s k -> putStr s >> uiRun k
run = uiRun apTest
It works OK however I would like to write the "application" apTest using a monad instead of using $. In other words like this:
apTest = do
ioWriteLn "Hello world!"
i <- ioRead
ioWriteLn $ "You wrote [" ++ i ++ "]"
return 10
The problem is that the code is resisting all my attempts to turn the "function style" DSL into a monad. So the question is how to implement a monad instance for this DSL that allows you to write apTest monad style instead of "$" style?
Sure it's a monad. In fact it would be simpler to express it as a free monad [1] but we can work with what you've got.
Here's how we know it's a monad: if you have a type data Foo a = ...
where Foo
represents some sort of recursive tree structure where a
s only occur at the leaves then you have a monad. return a
is "give me a tree consisting of one leaf labelled by a
", and >>=
is "substitution at the leaves".
In your case Ap
is a tree structure where
ApReturn a
is a leafthere are two sorts of interior nodes
ApRead
is a node which has no label and has one descendant coming off it for every value of typeString
ApWrite
is a node which is labelled by aString
and has only one descendant coming off it
I've added the monad instance to your code below. return
is just AppReturn
(plus the Ap
wrapper). >>=
is just recursively applying >>=
and substituting at the leaves.
A couple of hints for the future
- Put type signatures on everything top-level. Your colleagues, Stack Overflow commenters and your future self with thank you.
- The
Ap
wrapper was unnecessary. Consider removing it.
Enjoy!
data Ap a = Ap { runAp :: ApStep a }
data ApStep a =
ApRead (String -> Ap a)
| ApReturn a
| ApWrite String (Ap a)
ioRead k = Ap $ ApRead k
ioReturn a = Ap $ ApReturn a
ioWrite s k = Ap $ ApWrite s k
ioWriteLn s = ioWrite (s ++ "\n")
apTest =
ioWriteLn "Hello world!" $
ioRead $ \i ->
ioWriteLn ("You wrote [" ++ i ++ "]") $
ioReturn 10
uiRun ap =
case runAp ap of
ApRead k -> uiRun (k "Some input")
ApReturn a -> return a
ApWrite s k -> putStr s >> uiRun k
run = uiRun apTest
instance Monad Ap where
return = Ap . ApReturn
Ap (ApReturn a) >>= f = f a
Ap (ApRead r) >>= f = Ap (ApRead (\s -> r s >>= f))
Ap (ApWrite s a) >>= f = Ap (ApWrite s (a >>= f))
monadRead = Ap (ApRead (\s -> return s))
monadWrite s = Ap (ApWrite s (return ()))
monadWriteLn = monadWrite . (++ "\n")
apTestMonad = do
monadWriteLn "Hello world!"
i <- monadRead
monadWriteLn $ "You wrote [" ++ i ++ "]"
return 10
monadRun = uiRun apTestMonad
[1] http://www.haskellforall.com/2012/06/you-could-have-invented-free-monads.html
这篇关于如何将函数DSL转换为Haskell中的Monad?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!