gpt4 book ai didi

javascript - 为什么 Douglas Crockford 的 monad 演示代码需要 monad 原型(prototype)?

转载 作者:搜寻专家 更新时间:2023-11-01 04:29:42 24 4
gpt4 key购买 nike

注意:虽然这个问题中的代码涉及函数式编程/monad 等,我不是在问函数式编程(我也不认为这个问题应该有标签相关函数式编程等)。相反,我问的是 JavaScript 原型(prototype)的使用

代码源

我正在观看 Douglas Crockford 的名为“Monads and Gonads”的视频(在 YouTube 上为 herehere)。他在 JavaScript 中包含了一个 monad 的演示实现,如下所示。

monad对象及其原型(prototype)

在他的代码中,他使用 Object.create(null) 创建了一个真正的空对象并将其用作他最终 monad 的原型(prototype)目的。他附上 bind方法添加到 monad 对象本身,但是以后使用 lift 附加到 monad 的任何自定义函数不是附加到 monad 对象本身,而是附加到它的原型(prototype)。

需要原型(prototype)吗?

在我看来,使用原型(prototype)是不必要的复杂性。为什么不能将这些自定义函数直接附加到 monad 对象本身?然后,在我看来,原型(prototype)就不需要了,我们可以简化代码。

移除原型(prototype)后令人费解的结果

我尝试实现这种简化并得到了令人费解的结果。不使用原型(prototype)的代码有时仍然有效,即当自定义函数在没有额外参数(monad2.log())的情况下被调用时,它仍然可以使用 monad 包装的值(字符串“Hello world.”)。但是,当使用额外参数 (monad2.log("foo", "bar")) 调用自定义函数时,代码现在无法找到 value。即使它仍然可以使用那些额外的参数。

关于令人费解的结果的更新:部分是因为@amon 的回答,我意识到令人费解的结果不会出现,因为我改变了参数的数量,而是因为我只需重复调用 lift monad 上的 ed 方法(无论参数数量是否已更改)。因此,运行 monad2.log()连续两次将第一次产生正确的值,但第二次将是未定义的。

问题

那么,为什么这段代码需要原型(prototype)呢?或者,或者,消除原型(prototype)是如何导致 value 的?有时可以访问但其他时间不能访问?

演示代码说明

代码的两个版本如下所示。使用原型(prototype)的代码 ( MONAD1 ) 与 Crockford 在他的视频中使用的代码相同,只是附加的自定义函数是 console.log。而不是 alert这样我就可以在节点而不是浏览器中玩这个了。非原型(prototype)使用代码 ( MONAD2 ) 进行注释中指示的更改。输出显示在评论中。

使用原型(prototype)的代码

function MONAD1() {
var prototype = Object.create(null); // later removed
function unit (value) {
var monad = Object.create(prototype); // later moved
monad.bind = function (func, ...args) {
return func(value, ...args);
}
return monad;
}
unit.lift = function (name, func) {
prototype[name] = function (...args) { // later changed
return unit(this.bind(func, ...args));
};
return unit;
};
return unit;
}

var ajax1 = MONAD1()
.lift('log', console.log);

var monad1 = ajax1("Hello world.");

monad1.log(); // --> "Hello world."
monad1.log("foo", "bar"); // --> "Hello world. foo bar"

非原型(prototype)使用代码

function MONAD2() {
// var prototype = Object.create(null); // removed
var monad = Object.create(null); // new
function unit (value) {
// var monad = Object.create(prototype); // removed
monad.bind = function (func, ...args) {
return func(value, ...args);
}
return monad;
}
unit.lift = function (name, func) {
monad[name] = function (...args) { // changed
return unit(this.bind(func, ...args));
};
return unit;
};
return unit;
}

var ajax2 = MONAD2()
.lift('log', console.log);

var monad2 = ajax2("Hello world.");

monad2.log(); // --> "Hello world." i.e. still works
monad2.log("foo", "bar"); // --> "undefined foo bar" i.e. ???

JSBin

我在 node 中玩过这段代码,但你可以在 jsbin 中看到结果. Console.log在 jsbin 中的工作似乎与在终端节点中的工作不完全相同,但它仍然显示结果的相同令人费解的方面。 (如果您只单击控制台 Pane 中的“运行”,jsbin 似乎不起作用。相反,您必须通过单击“输出”选项卡激活输出 Pane ,然后单击“使用 js 运行” “输出” Pane 以在“控制台” Pane 中查看结果。)

最佳答案

您必须明确区分特定类型的 monad 和实际包含值的 monad 实例。您的第二个示例以我稍后将讨论的方式将两者混合在一起。

首先,MONAD 函数构造了一个新的 monad 类型。 “monad”这个概念本身并不是一种类型。相反,该函数创建了一个具有类似 monad 行为的类型:

  • unit 操作将值包装在 monad 中。它是一种构造函数:monadInstance = MonadType(x)。在 Haskell 中:unit::Monad m => a -> m a
  • bind 操作将函数应用于 monad 实例中的值。该函数必须返回相同类型的 monad。然后绑定(bind)操作返回新的 monad:anotherMonadInstance = monadInstance.bind(f)。在 Haskell 中:bind::Monad m => m a -> (a -> m b) -> m b

您可以认为 MonadTypeunit() 操作或多或少是同一件事。我们创建一个单独的原型(prototype)的原因是我们不想从“函数”类型继承随机包袱。此外,通过将它隐藏在 monad 类型的构造函数中,我们保护它免受未经检查的访问——只有 lift 可以添加新方法。

lift 操作不是必需的,但非常方便。它允许对普通值(不是 monad 实例)起作用的函数改为应用于 monad 实例。通常,它会返回一个在 monad 级别上运行的新函数:functionThatReturnsAMonadInstance = lift(ordinaryFunction)。在 Haskell 中:lift::Monad m => (a -> b) -> (a -> m b)。但是 lift 应该返回哪种 monad?为了保持这个上下文,每个提升的函数都绑定(bind)到一个特定的 MonadType。注意:不仅仅是一个特定的 monadInstance!一旦一个函数被提升,我们就可以将它应用到所有相同类型的 monad。

我现在将重写代码以使这些术语更加清晰:

function CREATE_NEW_MONAD_TYPE() {
var MonadType = Object.create(null);
function unit (value) {
var monadInstance = Object.create(MonadType);
monadInstance.bind = function (func, ...args) {
return func(value, ...args);
}
return monadInstance;
}
unit.lift = function (name, func) {
MonadType[name] = function (...args) {
return unit(this.bind(func, ...args));
};
return unit;
};
return unit;
}

var MyMonadType = CREATE_NEW_MONAD_TYPE()
MyMonadType.lift('log', console.log); // adds MyMonadType(…).log(…)

var monadInstance = MyMonadType("Hello world.");

monadInstance.log(); // --> "Hello world."
monadInstance.log("foo", "bar"); // --> "Hello world. foo bar"

在您的代码中发生的事情是您摆脱了 monadInstance。相反,您将 bind 操作添加到 MonadType!此 bind 操作恰好引用了用 unit() 包装的 last 值。

现在请注意,提升函数的返回值被包装为一个带有 unit 的 monad。

  • 当您构造 monadInstance(实际上是 MonadType)时,MonadType.bind() 指的是 “Hello世界” 值。
  • 您调用提升的 log() 函数。它接收 monad 中的值,这是用 unit() 包装的最后一个值,即 "Hello World"。提升函数的返回值 (console.log) 用 unit() 包装。此返回值为 undefined。然后用引用 undefined 值的新 bind 替换 bind 函数。
  • 您调用提升的 log() 函数。它接收 monad 中的值,这是用 unit() 包装的最后一个值,它是 undefined。观察到的输出随之而来。

关于javascript - 为什么 Douglas Crockford 的 monad 演示代码需要 monad 原型(prototype)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40407352/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com