通过Identity Monad在Haskell中迭代替换

时间:2011-03-25 10:47:40

标签: haskell monads

据我所知,monadic绑定操作的一个重要用途是进行变量替换,例如

do x <- maybeComputeSomething
   y <- maybeComputeSomethingElse
   maybeDoStuff x y

如果mayComputeSomething,maybeComputeSomethingElse和maybeDoStuff都返回Maybe值,这一切都很好,但我觉得这种迭代替换形式在计算中很有用,即使没有返回Nothing的可能性。由于身份是一个单子,我在某种意义上希望能够做一些像

这样的事情
do x <- computeSomething
   y <- computeSomethingElse
   doStuff x y

会扩展到类似

的东西
computeSomething >>= (\x -> 
    computeSomethingElse >>= (\y ->
        doStuff x y))

因为身份monad中的绑定只是x>&gt; = = f = f x,所以这将作为

doStuff computeSomething computeSomethingElse

如果&gt;&gt; =被视为身份monad的绑定,但这(不出所料)失败,因为毕竟我从未明确声明monad(与Maybe示例不同,当类型显然是形式为Maybe a)和如果Haskell在任何地方都采用了身份monad,那么大部分案例都会因所需的歧义而迅速混乱。

所以这引出了我的问题。我想要一些感觉像

的代码
do x <- computeSomething
   y <- computeSomethingElse
   doStuff x y

完全相同
doStuff computeSomething computeSomthingElse

据我所知,我可以通过明确定义一个看起来像Maybe的Id monad来做到这一点,除非没有Nothing可能性,但这需要类型a - &gt; Id a,所以并没有真正定义身份monad。此外,我的功能现在必须有类型a - &gt; Id b,而我真的希望他们仍然是a - &gt;湾有没有办法在不引入相关类型的复杂性的情况下创建重复的替换感觉?

2 个答案:

答案 0 :(得分:7)

遗憾的是,身份monad至少需要一个newtype包装器来发送。

当您考虑重叠实例时,原因有意义。以某种方式为

构造的monad实例
type Id a = a 

永远适用,包含任何更有趣的monad。

因此你必须像

那样实现它
newtype Id a = Id a

然后

instance Monad Id where
    return = Id
    Id a >>= f = f a

这为类型系统提供了足够的提示,以便知道您何时真正想要使用此monad。注意:这是类型类调度的工件。在提供类似ML的模块或Scala的语言中,你没有类型推断来帮助(反过来担心)你可以定义一个真正的身份monad,但是你被迫将所有的调用路由到那个特定的&gt; &gt; =由你自己,所以无论如何它变成洗涤或净损失。

现在你可以

do x <- computeSomething
   y <- computeSomethingElse
   doStuff x y

但类型是

computeSomething :: Id a
computeSomethingElse :: Id b
doStuff :: a -> b -> Id c

如果您将Id设置为Applicative的实例,则语法可以缩减为

doStuff <$> computeSoemthing <*> computeSomethingElse

所以这些方法中的每一个都必须显式地应用Id newtype包装器或将它们的结果包装回来。

在实践中,最好在此上下文中使用let绑定。

let
   x = computeSomething
   y = computeSomethingElse
in doStuff x y

或更具惯用性,where子句

doStuff x y where
  x = computeSomething
  y = computeSomethingElse

甚至在这种情况下,因为它们非常小,并且完全循环到上面的应用示例:

doStuff computeSomething computeSomethingElse

答案 1 :(得分:1)

请注意,这确实是一个评论,但我已经让它成为允许格式化的答案......

看起来你只想要一个类似于 liftM2 的功能,除了后合并操作是monadic而不是纯粹的 - 例如

monady2 :: Monad m => (a -> b -> m c) -> m a -> m b -> m c

与liftM2比较,其中后合并器(第一个参数)是纯粹的:

liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c

monady2 组合子在标准库中不存在,但您可以自由定义它。