没有单子的符号:可能吗?

时间:2011-06-22 00:51:37

标签: haskell monads

我有>>>>=运算符的有状态类型,几乎是一个monad。预期用途是为另一种语言生成代码,并且使用该标记非常合适。

但是,没有明确定义的返回函数,因为值只应与副作用一起产生。因此,如果我伪造一个返回函数,return函数应该只返回一个错误,并且它违反了monad定律。 (即,使用返回函数永远无效。)

我观察到的是,记号只会使两个运算符,即>>>>=运算符。

有没有办法只为那些2个操作符保留类似符号的东西,或者像它一样干净,但没有制作monad?

注意:行下方是标题中不需要的详细信息。这就是为什么我问这个问题的背景,回答我感兴趣的问题。浏览这个主题的其他人可能更愿意忽略它。


更正:我没有将命令生成为命令式语言,尽管它无关紧要。我正在为Javascript生成代码。

澄清(响应bdonlan):我的类型是(ma),它带有一个状态(这是给定各种参数生成的代码,类似于状态monad)并将返回一个值(变量名称在生成的代码,附加了haskell类型)。因此,不应该有一种方法来返回与变量名相关联的值,而不会生成定义变量名的代码。 *这完全是由于我的设计,可能不如我未想到的其他设计。

回应回应:似乎有两种回应。首先,是否确实是可能的,也许是使用符号的最佳方式。第二个是关于使用monad是否更好,并定义一个返回(或者是否有意义不这样做 - 可能在以后的某个时间点,我会发现需要返回)。

我将要做的事情的例子:

data JsState = JsState { code :: String , vidCount :: Int } 
-- vidCount is for generating unique variable names

newtype JsStmt = JsStmt ( JsState -> JsState )

newtype JsMonad a = JsMonad ( JsState -> ( a , JsState ) )

def :: (JsValue a) => a -> JsMonad a

-- Outputs "var " ++ toUniqueVarName vidCount ++ " = " toCode a ++ ";"

-- Returns JsMonad with the variable name as the value,
-- with a type attached, and the JsState's vidCount is incremented by 1.


alertJsInt :: JsIntE -> JsMonad ()

-- Outputs something like "alert(" ++ toCode JsIntE ++ ");"

do
  x <- def $ JsIntL 2
  y <- def $ JsIntL 4
  alertJsInt (x + y)

5 个答案:

答案 0 :(得分:21)

嗯,你正在使用的那种结构已经存在;事实上是relevant type class can be found on Hackage。我不建议尝试强制它进入Monad的实例,因为bdonlan给出的原因。 return的存在非常重要,你可能会在试图使用Monad的标准函数时遇到麻烦。

...然而!也就是说,如果您真的想要do符号,请考虑此引用from the GHC user guide

  

“Do”符号使用任何函数(&gt;&gt; =),(&gt;&gt;)和失败进行翻译,都在范围内(不是Prelude版本)。

......换句话说,使用do符号是正确的,因为在desugared时它实际上没有使用return。该部分是关于RebindableSyntax扩展名,它完全符合其声称的内容。如果您不介意放弃do的{​​{1}}表示法,则可以使用该扩展程序,定义自己的函数,并使用所有您喜欢的Monad表示法。

答案 1 :(得分:9)

惯用解决方案是使用两个monad Writer和State-Writer来累积代码“String”和State来跟踪变量counter。

如果您不想使用monad变换器库,可以将两个monad组合成一个合并的monad:

-- There are better types to use rather than String...
type Output = String
type St  = Int

newtype JsMonad a = JSMonad { getJSMonad :: St -> (a, St, Output) }

[当你使用两个标准monad的标准组合时,返回和绑定的明显定义不会违反monad法则。]

这是构建使用该注释的嵌入式DSL的一种常见模式,例如,请参阅Hackage上的Andy Gill的Dot库。 Oleg Kiselyov也有这种风格的XML库(CSXML - 不是Hackage)。

由于您不关心最终答案,因此通常会编写一个忽略它的“运行”功能:

runJS :: JSmonad a -> Output
runJS ma = post $ getJSMonad ma 0
  where
    post (_,_,w) = w

有一点需要注意的是输出代码只是一个“日志” - 你在构建它时无法检查它,所以这确实限制了你可以做的一些事情。

答案 2 :(得分:5)

在没有return的情况下使用do notation将非常困难。考虑以下常见模式:

foo = do
    x <- bar
    y <- quux
    return $ x + y

如果没有回报,就无法定义。回想一下,>>=的类型为(>>=) :: Monad m => m a -> (a -> m b) - 它必须在monad中返回一个值。因此,即使我们在没有记号的情况下写出来,我们也会遇到同样的问题:

foo = bar >>= \x -> quux >>= \y -> (???????) (x + y)

因此,为此使用do-notation并不是一个好主意。特别是,没有什么可以阻止编译器实现引入符合monad定律的转换return(假设存在return)。

此外,即使定义&gt;&gt; =也很难。您将如何将其翻译成您的命令式语言?请记住,&gt;&gt; =在其右侧参数上采用任意函数;您无法检查此函数以告知它可能执行的操作,因此无法将此函数的主体转换为命令式语言。我想你可以将参数的类型约束为某种跟踪数据类型,并尝试找出函数的效果,但现在这意味着函数无法检查其参数。简而言之,它不会很好地运作。

您可能考虑的另一种方法是创建一个Monad,它代表构建命令功能的过程。例如,它可能看起来像这样:

emitAddAndPrint varA varB = do
    varTmp <- allocateTempVariable
    emitOp (Add varTmp varA varB)
-- if you want to be fancy, emitOp (varTmp :=: varA :+: varB) or something
    emitCall "print" [varTmp]

答案 3 :(得分:4)

听起来你有arrow。在monad你需要能够写:

do
   x <- return $ f y
   if x then something else notSomething

“if”条件作为“do”的评估的一部分进行评估,结果是“something”或“notSomething”,但不能同时进行。但是,对于代码生成,您可能希望将“if”转换为生成的代码,并对两个分支进行求值,以生成可在运行时进行选择的代码。

箭头的等效代码涉及使用ArrowChoice类,您可以在其中访问条件和两个分支,这就是您需要的。

答案 4 :(得分:3)

如果您可以将类型设为Monad的实例,即使缺少returndo符号也会有效。我已经完成了它(例如BASIC),这是获得DSL命令式表示法的绝佳方式。