Haskell Monad绑定运营商混淆
好的,所以我不是Haskell程序员,但是我对Haskell背后的很多想法深感兴趣,并且正在研究它。 但是我陷入了一个尴尬的境地:我似乎无法将我的头围绕Monads,这似乎是相当重要的。 我知道有这么多问题要求解释Monads,所以我会更具体地说明有什么问题:
我阅读了这篇优秀的文章(Javascript中的介绍),并认为我完全理解了Monads。 然后我阅读Monads上的维基百科条目,并看到:
一个多态类型(M t)→(t→M u)→(M u)的绑定操作,Haskell用中缀操作符>> =表示。 它的第一个参数是一个monadic类型的值,第二个参数是一个函数,它从第一个参数的基础类型映射到另一个monadic类型,其结果在另一个monadic类型中。
好的,在我引用的文章中,绑定是一个只有一个参数的函数。 维基百科说了两个。 我认为我对Monads的理解如下:
但是肯定有什么不对的,因为我的绑定概念有一个参数:一个函数。 但是(根据维基百科)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由两个相互关联的功能组成:绑定和单元。
是和不是。 bind
和unit
的组合足以定义Monad,但join
, fmap
和unit
的组合同样充分。 事实上,后者就是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
返回一个带有元组的函数,然后模式匹配将其解包为x
和s
,我希望这是可以理解的):
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