Javascript 为什么函数实例的bind将原始值提供给下一次计算?

Javascript 为什么函数实例的bind将原始值提供给下一次计算?,javascript,haskell,functional-programming,bind,monads,Javascript,Haskell,Functional Programming,Bind,Monads,作为一个对Haskell只有模糊理解的函数式Javascript开发人员,我真的很难理解像Monad这样的Haskell习语。当我查看函数实例的>=时 (>>=) :: (r -> a) -> (a -> (r -> b)) -> r -> b instance Monad ((->) r) where f >>= k = \ r -> k (f r) r // Javascript: Javascript及其应用

作为一个对Haskell只有模糊理解的函数式Javascript开发人员,我真的很难理解像Monad这样的Haskell习语。当我查看函数实例的
>=

(>>=)  :: (r -> a) -> (a -> (r -> b)) -> r -> b

instance Monad ((->) r) where
f >>= k = \ r -> k (f r) r

// Javascript:
Javascript及其应用

const bind = f => g => x => g(f(x)) (x);

const inc = x => x + 1;

const f = bind(inc) (x => x <= 5 ? x => x * 2 : x => x * 3);


f(2); // 4
f(5); // 15
constbind=f=>g=>x=>g(f(x))(x);
常数inc=x=>x+1;
常数f=bind(inc)(x=>x*2:x=>x*3);
f(2);//4.
f(5);//15
一元函数
(a->(r->b))
(或
(a->mb)
)提供了一种根据先前结果选择下一次计算的方法。更一般地说,一元函数及其相应的
bind
运算符似乎使我们能够定义函数组合在特定计算上下文中的含义

更令人惊讶的是,一元函数没有将上一次计算的结果提供给下一次计算。而是传递原始值。我希望
f(2)
/
f(5)
产生
6
/
18
,类似于正常的函数组合。这种行为是单子函数特有的吗?我误解了什么

更一般地说,一元函数及其相应的绑定 运算符似乎使我们能够定义什么函数 组合是指在特定的计算上下文中

我不知道你所说的“一元函数”是什么意思。单子(在Haskell中由一个绑定函数和一个纯函数组成)可以让您表达一系列单子动作如何链接在一起(单子相当于合成,相当于单子的
(..
)。从这个意义上讲,您确实获得了某种组合,但只获得了动作的组合(函数的形式为
a->mb

(关于
a->mb
类型的函数的新类型进一步抽象了这一点。它确实可以让您将一元动作的顺序编写为合成。)

我希望f(2)/f(5)产生6/18,类似于正常的函数组合

然后,你就可以用普通函数组合了!如果你不需要单子,就不要用单子

更令人惊讶的是,一元函数没有提供 上一次计算的结果与下一次计算的结果一致。相反 原始值已传递。。。这种行为是特定于 作为单子的功能


是的,就是它。monad
monad((->)r)
也被称为“reader monad”,因为它只从环境中读取数据。也就是说,就单子而言,您仍然在将上一个操作的单子结果传递给下一个操作-但这些结果本身就是函数

正如chi已经提到的,这一行

const f = bind(inc) (x => x <= 5 ? x => x * 2 : x => x * 3);

(不确定语法)

我认为您的困惑是因为使用了太简单的函数。特别是你写的

const inc = x => x + 1;
其类型是一个函数,该函数返回与其输入相同空间中的值。比如说,
inc
正在处理整数。因为它的输入和输出都是整数,如果您有另一个接受整数的函数
foo
,很容易想象使用
inc
的输出作为
foo
的输入

然而,现实世界包含了更多令人兴奋的功能。考虑函数“代码> TreEyfOfStime,它取一个整数并创建该深度字符串的树。(我不会尝试实现它,因为我不知道足够的javascript来完成令人信服的工作。)现在突然很难想象将
tree\u of u depth
的输出作为
foo
的输入,因为
foo
需要整数,而
tree\u of u depth
需要生成树,对吗?我们唯一可以传递给
foo
的是
tree\u of theu depth
的输入,因为即使在运行
tree\u of theu depth
之后,这也是唯一的整数

让我们看看绑定的Haskell类型签名是如何体现的:

(>>=) :: (r -> a) -> (a -> r -> b) -> (r -> b)
这意味着
(>>=)
接受两个参数,每个参数都是函数。第一个函数可以是您喜欢的任何旧类型——它可以接受
r
类型的值,并生成
a
类型的值。特别是,您不必保证
r
a
是完全相同的。但是一旦选择了它的类型,那么
(>>=)
的下一个函数参数的类型就会受到约束:它必须是两个参数的函数,其类型与前面的
r
a
相同

现在您可以了解为什么我们必须将类型为
r
的相同值传递给这两个函数:第一个函数生成一个
a
,而不是更新的
r
,因此我们没有类型为
r
的其他值传递给第二个函数!与您使用
inc
的情况不同,第一个函数碰巧也生成了
r
,我们可能会生成其他一些非常不同的类型

这解释了为什么bind必须以这种方式实现,但可能无法解释为什么monad是一个有用的monad。其他地方也有关于这方面的文章。但是规范用例是针对配置变量的。假设在程序开始时解析一个配置文件;然后,对于程序的其余部分,您希望能够通过查看配置中的信息来影响各种函数的行为。在所有情况下,使用相同的配置信息都是有意义的——它不需要更改。然后这个monad变得很有用:您可以拥有一个隐式的配置值,monad的bind操作确保您正在排序的两个函数都可以访问该信息,而无需手动将其传递给这两个函数

附言,你说呢

更令人惊讶的是,一元函数没有提供previo的结果
const inc = x => x + 1;
(>>=) :: (r -> a) -> (a -> r -> b) -> (r -> b)