【问题标题】:Does Haskell always know which 'return' to call?Haskell 是否总是知道要调用哪个“返回”?
【发布时间】:2011-10-20 19:47:39
【问题描述】:

我正在定义一个 monad 的实例,如下所示:

data Something = Something a

instance Monad Something where
    return a = Something a        --Wraps a in 'Something', correct?
    m >>= f = do
        var <- m
        return $ f var            --I want this to pass var to f, then wrap the result
                                  --back up in the 'Something' monad with the return I
                                  --Just defined

问题是->

1:我所做的事情是否有任何明显的错误/误解?

2:Haskell 是否知道从 m &gt;&gt;= f 调用我上面定义的返回值

3:如果我出于某种原因定义了另一个函数

f :: Something a -> Something b
f x = do
    var <- x
    return $ doMagicTo x

return 会调用我在 monad 实例中定义的 return 并将 x 包装在 Something 中吗?

【问题讨论】:

    标签: haskell monads


    【解决方案1】:

    这里有几个大问题。

    首先,Monad 实例必须具有 kind * -&gt; *。这意味着他们至少需要一个类型变量,而您的Something 没有。比较:

    -- kind * -> *
    Maybe
    IO
    Either String
    
    -- kind *
    Maybe Int
    IO ()
    Either String Double
    

    看到MaybeIOEither String 中的每一个都需要一个类型参数才能使用它们吗?使用Something,没有地方可以填写类型参数。因此您需要将定义更改为:

    data Something a = Something a
    

    第二个大问题是你的 Monad 实例中的 &gt;&gt;= 是错误的。您通常不能使用 do-notation,因为它只会调用 Monad 函数 return&gt;&gt;=。因此,您必须在没有任何 monad 函数的情况下将其写出来,无论是 do-notation 还是调用 &gt;&gt;=return

    instance Monad Something where
        return a = Something a        --Wraps a in 'Something'
        (Something m) >>= f = f m     --unwraps m and applies it to f
    

    &gt;&gt;= 的定义比你想象的要简单。展开 m 很容易,因为您只需要在 Something 构造函数上进行模式匹配。还有f :: a -&gt; m b,所以您不必担心再次包装它,因为f 会为您完成。

    虽然没有办法一般解开一个monad,但可以解开很多特定的monad。

    请注意,在 monad 实例声明中使用 do-notation 或 &gt;&gt;= 在语法上没有任何问题。问题是&gt;&gt;= 是递归定义的,因此当您尝试使用它时程序会进入无限循环。

    (注意这里定义的SomethingIdentity monad

    对于您的第三个问题,是的,在 Monad 实例中定义的 return 函数将被调用。类型类是按类型分派的,并且由于您已指定类型必须为 Something b,编译器将自动使用 Something 的 Monad 实例。 (我想你的意思是最后一行是doMagicTo var)。

    【讨论】:

    • 这很棒。所有这些答案都很好。我不得不说,到目前为止,Haskell 社区是我所遇到的最有帮助和最彻底的社区。干杯,伙计们。
    【解决方案2】:

    主要问题是您对&gt;&gt;= 的定义是循环的。

    Haskell 的 do 语法是 &gt;&gt;=&gt;&gt; 链的语法糖,所以你的定义

    m >>= f = do
        var <- m
        return $ f var         
    

    脱糖

    m >>= f = 
        m >>= \var -> 
        return $ f var
    

    所以您将m &gt;&gt;= f 定义为m &gt;&gt;= \...,这是循环的。

    您需要对&gt;&gt;= 做的是定义如何从m 中提取一个值以传递给f。此外,您的f 应该返回一个单子值,因此在这里使用return 是错误的(这更接近于您定义fmap 的方式)。

    &gt;&gt;=Something 的定义可以是:

    (Something a) >>= f = f a
    

    这就是 Identity Monad - 有很多关于它的文章 - 这是了解 monad 如何工作的一个很好的起点。

    【讨论】:

      【解决方案3】:
      1. 关闭。 return 在这里是多余的,您需要在 Something 类型构造函数中添加类型参数。 编辑:这段代码仍然是错误的。 &gt;&gt;= 的定义是循环的。有关详细信息,请参阅其他答案。

        data Something a = Something a
        
        instance Monad Something where
            return a = Something a
            m >>= f = do
                var <- m
                f var
        
      2. 由于您对&gt;&gt;= 的定义在instance Monad Something where 下,所以&gt;&gt;= 的类型是Something a -&gt; (a -&gt; Something b) -&gt; Something b。所以它可以看出f var 必须是Something b 类型。在这里谷歌的术语是“类型推断”

      3. 是的。同样,这是类型推断。如果编译器不能推断出你想要的类型,它会告诉你。但通常可以。

      【讨论】:

      • 请参阅 John L 的解决方案:您不能使用 do-block 来定义 &gt;&gt;= 这样,因为您认为 do-block 是如何脱糖的? (在rampion的解决方案中也有说明。)
      • 噢,好电话。我现在想得更像是一个 Haskell 程序员,因为我只是拿了 OP 的代码,修改它直到它经过类型检查和编译,然后说:“是的,那一定是对的。” :P 不过,我认为 OP 的问题或多或少是“Haskell 是否进行类型推断?”尽管他们没有那样问。我的主要观点是,是的,确实如此。
      猜你喜欢
      • 2020-12-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-06
      • 1970-01-01
      • 2021-11-23
      • 2015-07-06
      • 1970-01-01
      相关资源
      最近更新 更多