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

Din*_*uek 23 monads haskell

我有一个有状态类型的>>>>=运算符,几乎是一个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)
Run Code Online (Sandbox Code Playgroud)

C. *_*ann 21

那么,你正在使用的那种结构已经存在; 一个相关类型的类可以在Hackage发现,其实.我建议不要试图强迫它进入一个实例Monad,因为bdonlan给出的原因.存在return是非常基础的,你可能会在尝试使用Monads的标准函数时遇到麻烦.

....然而!也就是说,如果你真的想要do符号,请考虑GHC用户指南中的这句话:

"Do"符号使用任何函数(>> =),(>>)进行转换,并且失败,在范围内(不是Prelude版本).

......换句话说,你使用do符号是有道理的,因为return在desugared时它实际上没有使用它.该部分是关于RebindableSyntax扩展,它完全符合它的要求.如果您不介意放弃do表示法Monad,可以使用该扩展名,定义自己的功能,并使用do您喜欢的符号.

  • @Dingfeng Quek:`RebindableSyntax`不需要顶级定义,它只关心范围内的内容.随意做`foo = do {...} where(>> =)= ...`等. (12认同)

ste*_*ley 9

惯用的解决方案是使用两个monad Writer和State-Writer来累积代码"String"和State来跟踪变量计数器.

如果您不想使用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) }
Run Code Online (Sandbox Code Playgroud)

[当你使用两个标准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
Run Code Online (Sandbox Code Playgroud)

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


bdo*_*lan 5

如果没有,那就很难使用return.考虑以下常见模式:

foo = do
    x <- bar
    y <- quux
    return $ x + y
Run Code Online (Sandbox Code Playgroud)

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

foo = bar >>= \x -> quux >>= \y -> (???????) (x + y)
Run Code Online (Sandbox Code Playgroud)

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

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

您可以考虑的另一种方法是创建一个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]
Run Code Online (Sandbox Code Playgroud)