Haskell Monad绑定运营商混淆

好的,所以我不是Haskell程序员,但是我对Haskell背后的很多想法深感兴趣,并且正在研究它。 但是我陷入了一个尴尬的境地:我似乎无法将我的头围绕Monads,这似乎是相当重要的。 我知道有这么多问题要求解释Monads,所以我会更具体地说明有什么问题:

我阅读了这篇优秀的文章(Javascript中的介绍),并认为我完全理解了Monads。 然后我阅读Monads上的维基百科条目,并看到:

一个多态类型(M t)→(t→M u)→(M u)的绑定操作,Haskell用中缀操作符>> =表示。 它的第一个参数是一个monadic类型的值,第二个参数是一个函数,它从第一个参数的基础类型映射到另一个monadic类型,其结果在另一个monadic类型中。

好的,在我引用的文章中,绑定是一个只有一个参数的函数。 维基百科说了两个。 我认为我对Monads的理解如下:

  • Monad的目的是采用具有不同输入和输出类型的功能并使其可组合。 它通过用单个monadic类型包装输入和输出类型来实现这一点。
  • Monad由两个相互关联的功能组成:绑定和单元。 绑定接受一个不可组合的函数f,并返回一个新的函数g,它接受一元类型作为输入并返回一元类型。 g是可组合的。 单位函数接受f期望的类型的参数,并将其包装为一元类型。 然后这可以传递给g,或者传递给g的任何组合。
  • 但是肯定有什么不对的,因为我的绑定概念有一个参数:一个函数。 但是(根据维基百科)Haskell的绑定实际上需要两个参数! 我的错误在哪里?


    你没有犯错。 这里要理解的关键思想是currying - 两个参数的Haskell函数可以用两种方式来看。 第一个是简单的两个参数的函数。 例如,如果您有(+) ,通常会将这看作是带两个参数并添加它们。 另一种看待它的方式是作为一个机器制造商。 (+)是一个函数,它接受一个数字,比如x ,并创建一个将添加x的函数。

    (+) x = y -> x + y
    (+) x y = (y -> x + y) y = x + y
    

    在处理monad时,有时候可能会更好一些,比如上面提到的ephemient,去考虑=<< ,翻转后的版本>>= 。 有两种方法来看待这个问题:

    (=<<) :: (a -> m b) -> m a -> m b
    

    这是两个参数的函数,而且

    (=<<) :: (a -> m b) -> (m a -> m b)
    

    它将输入函数转换为文章中提到的简单版本。 就像我之前解释的那样,它们就像(+)一样。


    请允许我推翻你对Monad的看法。 我真诚地希望你认识到我不想失礼; 我只是试图避免扼杀词语。

    Monad的目的是采用具有不同输入和输出类型的功能并使其可组合。 它通过用单个monadic类型包装输入和输出类型来实现这一点。

    不完全是。 当你用“Monad的目的”开始一个句子时,你已经走错了路。 Monad不一定有“目的”。 Monad只是一种抽象,是一种适用于某些类型而不适用于其他类型的分类。 Monad抽象的目的就是抽象。

    Monad由两个相互关联的功能组成:绑定和单元。

    是和不是。 bindunit的组合足以定义Monad,但joinfmapunit的组合同样充分。 事实上,后者就是Monads在分类理论中被描述的方式。

    绑定接受一个不可组合的函数f,并返回一个新的函数g,它接受一元类型作为输入并返回一元类型。

    再次,不完全。 一元函数f :: a -> mb是完美的可组合的,具有某些类型。 我可以使用函数g :: mb -> c进行后期撰写,以获得g . f :: a -> c g . f :: a -> c ,或者我可以用函数h :: c -> a来预编写它以获得f . h :: c -> mb f . h :: c -> mb

    但你得到的第二部分是绝对正确的: (>>= f) :: ma -> mb 。 正如其他人所指出的那样,Haskell的bind函数以相反的顺序获取参数。

    g是可组合的。

    嗯,是。 如果g :: ma -> mb ,那么你可以用函数f :: c -> ma预先编写它来获得g . f :: c -> mb g . f :: c -> mb ,或者你可以使用函数h :: mb -> c进行后h :: mb -> c以获得h . g :: ma -> c h . g :: ma -> c 。 请注意, c可以是mv形式,其中m是Monad。 我想当你说“可组合”时,你的意思是说“你可以编写任意长的这种形式的函数链”,这是真的。

    单位函数接受f期望的类型的参数,并将其包装为一元类型。

    这是一个迂回的说法,但是,这是对的。

    这个[将unit应用于某个值的结果]然后可以传递给g,或者传递给g的任何组合。

    再次,是的。 尽管Haskell通常不会调用unit (或Haskell, return ),然后将它传递给(>>= f)

    -- instead of
    return x >>= f >>= g
    -- simply go with
    f x >>= g
    
    -- instead of
    x -> return x >>= f >>= g
    -- simply go with
    f >=> g
    -- or
    g <=< f
    

    您链接的文章基于sigfpe的文章,该文章使用了bind的翻转定义:

    首先,我翻转了bind的定义,并将其写为“绑定”一词,而通常将其写为操作符>>= 。 所以bind fx通常写为x >>= f

    所以,Haskell bind将一个值包含在monad中,并返回一个函数,该函数接受一个函数,然后用提取的值调用它。 我可能会使用非精确的术语,因此使用代码可能会更好。

    你有:

    sine x = (sin x,     "sine was called.")
    cube x = (x * x * x, "cube was called.")
    

    现在,翻译你的JS绑定(Haskell自动执行currying,因此调用bind f返回一个带有元组的函数,然后模式匹配将其解包为xs ,我希望这是可以理解的):

    bind f (x, s) = (y, s ++ t)
                    where (y, t) = f x
    

    你可以看到它的工作原理:

    *Main> :t sine
    sine :: Floating t => t -> (t, [Char])
    *Main> :t bind sine
    bind sine :: Floating t1 => (t1, [Char]) -> (t1, [Char])
    *Main> (bind sine . bind cube) (3, "")
    (0.956375928404503,"cube was called.sine was called.")
    

    现在,我们来反转bind的参数:

    bind' (x, s) f = (y, s ++ t)
                     where (y, t) = f x
    

    你可以清楚地看到它仍然在做同样的事情,但语法有点不同:

    *Main> bind' (bind' (3, "") cube) sine
    (0.956375928404503,"cube was called.sine was called.")
    

    现在,Haskell有一个语法技巧,允许您使用任何函数作为中缀运算符。 所以你可以写:

    *Main> (3, "") `bind'` cube `bind'` sine
    (0.956375928404503,"cube was called.sine was called.")
    

    现在重命名bind'>>=(3, "") >>= cube >>= sine ),你有你在找什么。 正如你所看到的,通过这个定义,你可以有效地摆脱单独的组合运算符。

    将新事件翻译回JavaScript会产生类似这样的事情(再次注意,我只颠倒了参数顺序):

    var bind = function(tuple) {
        return function(f) {
            var x  = tuple[0],
                s  = tuple[1],
                fx = f(x),
                y  = fx[0],
                t  = fx[1];
    
            return [y, s + t];
        };
    };
    
    // ugly, but it's JS, after all
    var f = function(x) { return bind(bind(x)(cube))(sine); }
    
    f([3, ""]); // [0.956375928404503, "cube was called.sine was called."]
    

    希望这有帮助,而不是引入更多的混淆 - 关键是这两个绑定定义是等价的,只是在调用语法上有所不同。

    链接地址: http://www.djcxy.com/p/43353.html

    上一篇: Haskell Monad bind operator confusion

    下一篇: HaltingProblem in Agda?