我有>>
和>>=
运算符的有状态类型,几乎是一个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)
答案 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
的实例,即使缺少return
,do
符号也会有效。我已经完成了它(例如BASIC),这是获得DSL命令式表示法的绝佳方式。