我试图通过在Javascript中实现他们的函数实例来提高我对Applicative
和Monad
的理解。我对Haskell的了解有限,我希望我的问题完全有道理。
以下是Javascript中fmap
,<*>
和>>=
类型类的Functor
,Applicative
和Monad
的实现:
const fmap = f => g => x => f(g(x)); // B combinator
const apply = f => g => x => f(x) (g(x)); // S combinator
const bind = f => g => x => g(f(x)) (x); // ?
我不确定bind
是否是Haskell实现的正确翻译:
(>>=) :: (r -> a) -> (a -> (r -> b)) -> r -> b
instance Monad ((->) r) where
f >>= k = \ r -> k (f r) r
如果bind
是正确的,它是如何解释的?我知道Applicative
可以对有效的计算进行排序。我还知道Monad
还允许您根据前一个效果确定下一个效果。
我可以看到序列(Javascript中急切的评估顺序):
apply
:f(x)
... g(x)
... lambda(result of g)
... lambda
的结果bind
:f(x)
... g(result of f)
... lambda(x)
... lambda
的结果然而,bind
函数看起来很奇怪。为什么f
和g
以相反的方式嵌套?具体的Monad
行为(根据前一个行为确定下一个效果)如何反映在此实现中?实际上g(f(x)) (x)
看起来像是一个带有翻转参数的函数组合,其中g
是一个二元函数。
当我使用一元函数和二元函数应用apply
/ bind
时,它们会产生相同的结果。这没有多大意义。
答案 0 :(得分:5)
Lee's answer的几个脚注:
然而,
bind
函数看起来很奇怪。为什么f
和g
反过来嵌套?
因为bind
是倒退的。比较(>>=)
及其翻转版本(=<<)
:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
(=<<) :: Monad m => (a -> m b) -> m a -> m b
或者,在您的具体示例中:
(>>=) :: (r -> a) -> (a -> (r -> b)) -> (r -> b)
(=<<) :: (a -> (r -> b)) -> (r -> a) -> (r -> b)
虽然在实践中我们倾向于比(>>=)
更频繁地使用(=<<)
(因为(>>=)
,从语法上讲,它很适合那种通常用于构建的管道monad ),从理论的角度来看(=<<)
是最自然的写作方式。特别是,与fmap
/ (<$>)
和(<*>)
的相似之处和差异更为明显:
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(=<<) :: Monad f => (a -> f b) -> f a -> f b
当我使用一元函数和二元函数应用
apply
/bind
时,它们会产生相同的结果。这没有多大意义。
这是关于函数实例的偶然事实。让我们并排放置专门的签名:
(<*>) :: (r -> (a -> b)) -> (r -> a) -> (r -> b)
(=<<) :: (a -> (r -> b)) -> (r -> a) -> (r -> b)
Monad
超越了Applicative
,提供了根据之前的结果确定下一个效果的方法(而不是“之前的效果” - Applicative
可以做到这一点)。在这种情况下,效果由一个函数组成,该函数在给定类型r
的参数的情况下生成值。现在,由于可以翻转具有多个参数的函数(即返回函数的函数),因此(r -> (a -> b))
和(a -> (r -> b))
之间没有显着差异(flip
可以将一个变为一个其他),这使得Monad
的{{1}}实例完全等同于(->) r
实例。
答案 1 :(得分:4)
对于某些固定类型r -> a
,函数的monad实例中的值具有类型r
。赋给(a -> (r -> b))
的函数(>>=)
允许您根据当前值(函数r -> a
)选择要返回的下一个函数。 f r
的类型为a
,k (f r)
的类型为r -> b
,这是下一个要应用的功能。
因此,在您的代码中g(f(x))
是一个函数,它需要一个r
类型的参数。 bind
的调用者可以根据前一个函数返回的值选择此函数,例如
var inc = x => x + 1;
var f = bind(inc)(function(i) {
if(i <= 5) { return x => x * 2; }
else { return x => x * 3; }
});
该函数将被赋予x
作为输入,并可根据inc(x)
的结果选择计算中的下一个阶段,例如
f(2) //4;
f(5) //15;