为什么函数实例的绑定会为下一次计算提供原始值?
Why does bind of the function instance supply the original value to the next computation?
作为一个对[=28=]只有模糊理解的功能性Javascript开发人员,我真的很难理解像monads这样的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
单子函数(a -> (r -> b))
(或(a -> m b)
)提供了一种根据先前结果选择下一个计算的方法。更一般地说,monadic 函数及其相应的 bind
运算符似乎使我们能够定义函数组合在特定计算上下文中的含义。
更令人惊讶的是,monadic 函数没有将先前计算的结果提供给后续计算。相反,原始值被传递。我希望 f(2)
/f(5)
产生 6
/18
,类似于正常的函数组合。这种行为是否特定于作为 monad 的函数?我误解了什么?
More generally, the monadic function along with its corresponding bind
operator seems to give us the capability to define what function
composition means in a specific computational context.
我不确定 "monadic function" 是什么意思。 Monad(在 Haskell 中由绑定函数和纯函数组成)让您表达 如何将一系列 monadic 操作链接在一起 ((<=<)
is the monad equivalent of composition, equivalent to (.)
for the Identity
monad) .从这个意义上说,你确实有点组合,但只有 actions 的组合(形式为 a -> m b
的函数)。
(这在 Kleisli
newtype around functions of the type a -> m b
. Its category instance 中进一步抽象,真正让您将 monadic 动作的顺序写成组合。)
I'd expect f(2)/f(5) to yield 6/18, similar to normal function composition.
然后,你就可以使用普通的函数组合了!如果不需要,请不要使用 monad。
It is all the more surprising that the monadic function doesn't supply
the result of the previous computation to the subsequent one. Instead,
the original value is passed. ... Is this behavior specific to
functions as monads?
是的,是的。 monad Monad ((->) r)
也被称为 "reader monad" 因为它只从它的环境中 读取 。也就是说,就 monad 而言,您 是 仍然将前一个动作的 monadic 结果传递给后续动作——但这些结果本身就是函数!
正如 chi 已经提到的,这一行
const f = bind(inc) (x => x <= 5 ? x => x * 2 : x => x * 3);
像
这样会更清楚
const f = bind(inc) (x => x <= 5 ? y => y * 2 : y => y * 3);
函数的 Monad
实例基本上是 Reader
monad。您有一个取决于环境的值 x => x + 1
(它将环境加 1)。
您还有一个函数,根据其输入,returns 一个值取决于环境 (y => y * 2
) 或另一个值取决于环境 (y => y * 3
).
在您的 bind
中,您仅使用 x => x + 1
的结果在这两个函数之间选择。您没有直接 返回先前的结果。但是你可以,如果你返回常量函数,忽略它们的环境并返回一个固定值,仅取决于之前的结果:
const f = bind(inc) (x => x <= 5 ? _ => x * 2 : _ => x * 3);
(不确定语法)
我认为您的困惑是因为使用了过于简单的函数。特别是,你写
const inc = x => x + 1;
其类型是一个函数,returns 在 中的值与其输入相同 space。假设 inc
正在处理整数。因为它的输入和输出都是整数,如果你有另一个接受整数的函数 foo
,很容易想象使用 inc
的 output 作为 输入到foo
.
不过,现实世界包含更多令人兴奋的功能。考虑函数 tree_of_depth
,它接受一个整数并创建一个具有该深度的字符串树。 (我不会尝试实现它,因为我对 javascript 的了解还不够多,无法完成令人信服的工作。)现在突然间很难想象将 tree_of_depth
的输出传递为foo
的输入,因为 foo
需要整数而 tree_of_depth
正在生成树,对吗?我们唯一可以传递给 foo
的是 输入 到 tree_of_depth
,因为这是我们唯一的整数,即使在 运行 之后也是如此tree_of_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 的绑定操作确保你正在排序的两个函数都可以访问该信息,而不必手动将它传递给两个函数。
P.S。你说
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
得到 both m
的结果(作为它的第一个参数)和原始值(作为它的第二个参数)。
作为一个对[=28=]只有模糊理解的功能性Javascript开发人员,我真的很难理解像monads这样的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
单子函数(a -> (r -> b))
(或(a -> m b)
)提供了一种根据先前结果选择下一个计算的方法。更一般地说,monadic 函数及其相应的 bind
运算符似乎使我们能够定义函数组合在特定计算上下文中的含义。
更令人惊讶的是,monadic 函数没有将先前计算的结果提供给后续计算。相反,原始值被传递。我希望 f(2)
/f(5)
产生 6
/18
,类似于正常的函数组合。这种行为是否特定于作为 monad 的函数?我误解了什么?
More generally, the monadic function along with its corresponding bind operator seems to give us the capability to define what function composition means in a specific computational context.
我不确定 "monadic function" 是什么意思。 Monad(在 Haskell 中由绑定函数和纯函数组成)让您表达 如何将一系列 monadic 操作链接在一起 ((<=<)
is the monad equivalent of composition, equivalent to (.)
for the Identity
monad) .从这个意义上说,你确实有点组合,但只有 actions 的组合(形式为 a -> m b
的函数)。
(这在 Kleisli
newtype around functions of the type a -> m b
. Its category instance 中进一步抽象,真正让您将 monadic 动作的顺序写成组合。)
I'd expect f(2)/f(5) to yield 6/18, similar to normal function composition.
然后,你就可以使用普通的函数组合了!如果不需要,请不要使用 monad。
It is all the more surprising that the monadic function doesn't supply the result of the previous computation to the subsequent one. Instead, the original value is passed. ... Is this behavior specific to functions as monads?
是的,是的。 monad Monad ((->) r)
也被称为 "reader monad" 因为它只从它的环境中 读取 。也就是说,就 monad 而言,您 是 仍然将前一个动作的 monadic 结果传递给后续动作——但这些结果本身就是函数!
正如 chi 已经提到的,这一行
const f = bind(inc) (x => x <= 5 ? x => x * 2 : x => x * 3);
像
这样会更清楚const f = bind(inc) (x => x <= 5 ? y => y * 2 : y => y * 3);
函数的 Monad
实例基本上是 Reader
monad。您有一个取决于环境的值 x => x + 1
(它将环境加 1)。
您还有一个函数,根据其输入,returns 一个值取决于环境 (y => y * 2
) 或另一个值取决于环境 (y => y * 3
).
在您的 bind
中,您仅使用 x => x + 1
的结果在这两个函数之间选择。您没有直接 返回先前的结果。但是你可以,如果你返回常量函数,忽略它们的环境并返回一个固定值,仅取决于之前的结果:
const f = bind(inc) (x => x <= 5 ? _ => x * 2 : _ => x * 3);
(不确定语法)
我认为您的困惑是因为使用了过于简单的函数。特别是,你写
const inc = x => x + 1;
其类型是一个函数,returns 在 中的值与其输入相同 space。假设 inc
正在处理整数。因为它的输入和输出都是整数,如果你有另一个接受整数的函数 foo
,很容易想象使用 inc
的 output 作为 输入到foo
.
不过,现实世界包含更多令人兴奋的功能。考虑函数 tree_of_depth
,它接受一个整数并创建一个具有该深度的字符串树。 (我不会尝试实现它,因为我对 javascript 的了解还不够多,无法完成令人信服的工作。)现在突然间很难想象将 tree_of_depth
的输出传递为foo
的输入,因为 foo
需要整数而 tree_of_depth
正在生成树,对吗?我们唯一可以传递给 foo
的是 输入 到 tree_of_depth
,因为这是我们唯一的整数,即使在 运行 之后也是如此tree_of_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 的绑定操作确保你正在排序的两个函数都可以访问该信息,而不必手动将它传递给两个函数。
P.S。你说
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
得到 both m
的结果(作为它的第一个参数)和原始值(作为它的第二个参数)。