作为一个对 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
monadic 函数 (a -> (r -> b))
(或 (a -> m b)
)提供了一种根据以下条件选择下一个计算的方法之前的结果。更一般地说,monadic 函数及其相应的 bind
运算符似乎使我们能够定义函数组合在特定计算上下文中的含义。
更令人惊讶的是,monadic 函数没有将先前计算的结果提供给后续计算。相反,原始值被传递。我希望 f(2)
/f(5)
产生 6
/18
,类似于正常功能组成。这种行为是否特定于作为 monad 的函数?我误解了什么?
最佳答案
我认为您的困惑来自使用过于简单的函数。特别是,你写
const inc = x => x + 1;
它的类型是一个函数,它返回与输入相同的空间中的值。比方说 inc
正在处理整数。因为它的输入和输出都是整数,如果你有另一个函数foo
采用整数,很容易想象使用 inc
的输出作为 foo
的输入 .
不过,现实世界包含更多令人兴奋的功能。考虑函数 tree_of_depth
它接受一个整数并创建一个该深度的字符串树。 (我不会尝试实现它,因为我对 javascript 的了解还不够多,无法完成令人信服的工作。)现在突然之间,很难想象传递 tree_of_depth
的输出。作为 foo
的输入, 自 foo
期待整数和 tree_of_depth
正在生产树木,对吧?我们唯一可以传递给foo
的东西是 tree_of_depth
的输入 ,因为那是我们唯一的整数,即使在运行 tree_of_depth
之后也是如此.
让我们看看它是如何在绑定(bind)的 Haskell 类型签名中体现的:
(>>=) :: (r -> a) -> (a -> r -> b) -> (r -> b)
这表示 (>>=)
接受两个参数,每个参数都起作用。第一个函数可以是您喜欢的任何旧类型——它可以采用 r
类型的值。并产生 a
类型的值.特别是,您不必 promise r
和 a
完全一样。但是一旦你选择了它的类型,然后是 (>>=)
的下一个函数参数的类型。受约束:它必须是两个类型相同的参数的函数 r
和 a
和以前一样。
现在您可以明白为什么我们必须传递类型相同的值 r
对于这两个函数:第一个函数产生一个 a
,不是更新的 r
, 所以我们没有其他 r
类型的值传递给第二个函数!与您的情况不同 inc
,其中第一个函数恰好也产生一个r
,我们可能正在生产其他一些非常不同的类型。
这解释了为什么 bind 必须按原样实现,但可能没有解释为什么这个 monad 很有用。在其他地方有关于这一点的写作。但是规范的用例是配置变量。假设在程序启动时你解析了一个配置文件;然后对于程序的其余部分,您希望能够通过查看来自该配置的信息来影响各种功能的行为。在所有情况下,使用相同的配置信息都是有意义的——它不需要更改。然后这个 monad 变得有用:您可以有一个隐式配置值,并且 monad 的绑定(bind)操作确保您正在排序的两个函数都可以访问该信息,而无需手动将其传递给两个函数。
附言你说
It is all the more surprising that the monadic function doesn't supply the result of the previous computation to the subsequent one.
我觉得有点不精确:事实上在m >>= f
, 函数 f
得到两个 m
的结果(作为它的第一个参数)和原始值(作为它的第二个参数)。
关于javascript - 为什么函数实例的绑定(bind)会为下一次计算提供原始值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40473118/