- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
注意:虽然这个问题中的代码涉及函数式编程/monad 等,我不是在问函数式编程(我也不认为这个问题应该有标签相关函数式编程等)。相反,我问的是 JavaScript 原型(prototype)的使用。
代码源
我正在观看 Douglas Crockford 的名为“Monads and Gonads”的视频(在 YouTube 上为 here 或 here)。他在 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
。您可以认为 MonadType
和 unit()
操作或多或少是同一件事。我们创建一个单独的原型(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/
好吧,我怀疑这是一个独特的情况,所以要么有人这样做了,要么有人认为这是不可能的,至少以我所要求的方式。 我有 2 个原型(prototype)变量(函数),一个是父变量,另一个是助手。我想做的是从助手
这是 JavaScript 大师的问题。我正在尝试更优雅地使用 JavaScript 原型(prototype)模型。这是我的实用程序代码(它提供了真实的原型(prototype)链并正确使用 ins
我们知道在 JavaScript 中有一个用于数组的 .forEach() 方法。但是字符串没有内置该方法。 那么,下面的代码片段有没有问题:String.prototype.forEach = Ar
我们知道在 JavaScript 中有一个用于数组的 .forEach() 方法。但是字符串没有内置该方法。 那么,下面的代码片段有没有问题:String.prototype.forEach = Ar
我看到了两种不同的模式和解释。来自 DailyJS 和许多其他人的一篇:矩形.prototype = new Shape(); 然后是 Crockford 的 here 这意味着只是 矩形.proto
尝试在 Object.prototype 以及 String.prototype 和 Number.prototype 上定义一个 hashCode 方法>。我正在使用以下方法定义原型(prototy
在本教程中,您将借助示例了解 JavaScript 中的原型。 在学习原型之前,请务必查看以下教程: JavaScript 对象 JavaScript 构造函数 如您所知,您可以使用对象构造函
当构造新对象时,该对象被设置为委托(delegate)任何尚未显式设置为其构造函数原型(prototype)的属性。这意味着我们可以稍后更改原型(prototype),并且仍然可以看到实例中的更改。
我正在努力获得更好的 JavaScript 实用知识。所以,我买了 Douglas Crockford 的书“JavaScript the good parts”。 我现在很难掌握原型(prototy
我的理解是相同类型的所有对象将共享相同的原型(prototype)。因此对原型(prototype)的更改将反射(reflect)在每个对象上。但是值类型的属性似乎不是这样。这种属性是如何存储的? f
这个问题在这里已经有了答案: 关闭 12 年前。 Possible Duplicate: JavaScript: Class.method vs. Class.prototype.method 创建
为什么在 MDN 函数中 polyfills 使用“if (!Array.prototype.filter)”? if (!Array.prototype.filter) { Array.prot
这个问题已经有答案了: Assigning prototype methods *inside* the constructor function - why not? (6 个回答) 已关闭 7 年
关闭。这个问题需要更多focused .它目前不接受答案。 想改进这个问题吗? 更新问题,使其只关注一个问题 editing this post . 关闭 9 年前。 Improve this qu
面向对象有一个特征是继承,即重用某个已有类的代码,在其基础上建立新的类,而无需重新编写对应的属性和方法,继承之后拿来即用; 。 在其他的面向对象编程语言比如Java中,通常是指,子类继承父类的属性和
OOP 中原型(prototype)设计模式最重要的部分之一是我们不会从头开始创建新对象,我们只是使用 clone() 函数从现有对象克隆它们。 那么clone()函数是深拷贝还是浅拷贝? 如果它是一
在进行原型(prototype)设计时,您在多大程度上放弃了最佳实践来支持代码和修复黑客攻击?当然,代码并不打算在完整的生产环境中保留。 补充:我正在研究一个用 Python 制作的相当大的半工作原型
我开始学习设计模式。我知道原型(prototype)是用来制作我已经拥有的对象的精确副本,而享元是用来制作类似的对象。 我已经编写了 2D 平台游戏,例如马里奥(Java)。有很多相同的敌人,唯一的区
我正在使用 Maven 生成原型(prototype)。我能够使原型(prototype)生成正常,并且它生成的项目模板按预期工作。唯一的问题是在我的 shell 脚本中。脚本中注释掉的任何内容都会被
我想用 primefaces 配置一个 Java EE 项目。我在某处读到可以使用 mvn arechetype:generate 创建项目结构。当我使用它时,我只看到了 41 个选项,而在该教程中,
我是一名优秀的程序员,十分优秀!