如何将函数DSL转换为Haskell中的Monad? [英] How do I convert a functional DSL into a Monad in Haskell?

查看:105
本文介绍了如何将函数DSL转换为Haskell中的Monad?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下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 是一片叶子
  • >
  • 有两种内部节点


    1. ApRead 是一个节点,它没有标签,并且每个类型为 String

    2. ApWrite 是一个由字符串标记的节点,并且只有一个后裔离开它




我在下面的代码中添加了monad实例。 return 只是 AppReturn (加上 Ap 包装器)。 >> = 只是递归地应用>> = 并代替叶子。



未来的几点提示


  1. 将类型签名放在顶层的所有东西上。您的同事,Stack Overflow评论者以及您未来的自我感谢。

  2. 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 as 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 leaf
  • there are two sorts of interior nodes

    1. ApRead is a node which has no label and has one descendant coming off it for every value of type String
    2. ApWrite is a node which is labelled by a String 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

  1. Put type signatures on everything top-level. Your colleagues, Stack Overflow commenters and your future self with thank you.
  2. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆