【发布时间】:2016-11-03 16:34:57
【问题描述】:
注意:虽然这个问题中的代码涉及函数式编程/单子等,我不是在问函数式编程(我也不认为这个问题应该有相关的标签到函数式编程等)。相反,我问的是 JavaScript 原型的使用。
代码来源
我正在观看 Douglas Crockford 的名为“Monads and Gonads”的视频(在 YouTube 上的 here 或 here)。他在 JavaScript 中包含了一个 monad 的演示实现,如下所示。
monad 对象及其原型
在他的代码中,他使用Object.create(null) 创建了一个真正的空对象,并将其用作他最终的monad 对象的原型。他将 bind 方法附加到 monad 对象本身,但后来使用 lift 附加到 monad 的任何自定义函数都不是附加到 monad 对象本身,而是附加到它的原型。
需要原型吗?
在我看来,使用原型是不必要的复杂性。为什么不能简单地将这些自定义函数直接附加到 monad 对象本身?然后,在我看来,原型就不需要了,我们可以简化代码。
移除原型后的令人费解的结果
我尝试实现这种简化并得到令人费解的结果。不使用原型的代码有时仍然有效,即当调用自定义函数时没有额外参数 (monad2.log()) 时,它仍然可以使用 monad 包装的值(字符串“Hello world.”)。但是,当使用额外参数 (monad2.log("foo", "bar")) 调用自定义函数时,代码现在无法找到 value,即使它仍然可以使用这些额外参数。
关于令人费解的结果的更新:部分由于@amon 的回答,我意识到,令人费解的结果并没有出现,因为我更改了参数的数量,而是因为我只需在 monad 上重复调用 lifted 方法(无论参数数量是否已更改)。因此,连续运行两次monad2.log() 第一次会产生正确的值,但第二次会不确定。
问题
那么,为什么这段代码需要原型?或者,或者,消除原型如何导致 value 有时可访问,但有时不能访问?
演示代码说明
两个版本的代码如下所示。使用原型的代码 (MONAD1) 与 Crockford 在他的视频中使用的代码相同,只是附加的自定义函数是 console.log 而不是 alert,这样我就可以在 node 中而不是在浏览器中使用它。非原型使用代码 (MONAD2) 进行了 cmets 中指示的更改。输出显示在 cmets 中。
使用原型的代码
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"
不使用原型的代码
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 中的工作方式似乎与在终端中的 node 中的工作方式不同,但它仍然显示出同样令人费解的结果方面。 (如果您只单击控制台窗格中的“运行”,jsbin 似乎不起作用。相反,您必须通过单击“输出”选项卡来激活输出窗格,然后单击“使用 js 运行” 'Output' 窗格以在 'Console' 窗格中查看结果。)
【问题讨论】:
-
这与
Object.create()和unit.lift()以及unit(this.bind(func, args))中提到的this的相对时间有关。在我回答之前,我需要更多地使用它来解释原因 -
这是一个奇怪而混乱的 monad 实现。您应该查看Fantasy Land。各种 monad 有很多简洁明了的实现。
-
@ftor,我听说过 Fantasy Land 并将对其进行研究。为了让 Crockford 从怀疑中受益(他可能应该或可能不应该得到,我不知道),我猜他的 monad 实现很奇怪为了提供功能而故意混淆-programming-newbie JavaScript 开发人员是一个过于简单且可能已损坏的起点,只是为了向我们介绍 monad。如果您认为即使是对 JS 中 monad 的简单介绍也应该以不同的方式进行,请随时再次插话,但与此同时,我将继续将其拆开以开始“深入”FP。
-
@Andrew 首先,我认为您想了解 FP 真是太好了。我也是一个学习者。无论如何,Monad 由一个类型(在 JS 中通常是带有一些附加方法的原型)、一个表示该类型实例的复合类型(在 JS 中通常是
Object类型)和一个特定的效果/上下文组成,即这个特定的 Monad 首先存在的原因。效果/上下文决定了如何实现bind和unit。克罗克福德的例子没有反映出这一点——或者至少我看不到。 -
@ftor,写了一个特定的效果/上下文...如果我理解正确,Crockford 在他的视频中的其他地方讨论了这些事情。例如,他将
function MONAD() {...更改为function MONAD(modifier) {...以允许修饰函数改变单子/类型。我在这里展示的代码只是视频的一部分。正如您所建议的,他的“也许”单子修改了bind,以便在空值的情况下简单地返回单子实例。无论如何,您还给了我有用的类别:类型、复合类型、特定效果/上下文。这有助于我对其中一些概念进行分类。再次感谢。
标签: javascript prototype monads