And*_*ems 5 javascript monads prototype
注意:虽然这个问题中的代码涉及函数式编程/ monad等,但我不是在询问函数式编程(我也不认为这个问题应该有与函数式编程相关的标签等).相反,我问的是使用JavaScript的原型.
代码来源
我正在观看道格拉斯·克罗克福德的视频"Monads and Gonads"(在YouTube上这里或这里).他在JavaScript中包含了monad的演示实现,如下所示.
该monad
对象和它的原型
在他的代码中,他创建了一个真正空的对象,Object.create(null)
并使用它作为最终monad
对象的原型.他将bind
方法附加到monad对象本身,但是后来附加到monad使用的任何自定义函数都不lift
是附加到monad对象本身而是附加到它的原型.
需要原型吗?
在我看来,使用原型是不必要的复杂性.为什么这些自定义函数不能直接附加到monad对象本身?然后,在我看来,不需要原型,我们可以简化代码.
删除原型时令人费解的结果
我尝试实现这种简化并得到令人费解的结果.非原型使用代码有时仍然有效,即当调用自定义函数而没有额外的参数(monad2.log()
)时,它仍然可以使用monad-wrapped值(字符串"Hello world." ).但是,当使用额外的参数(monad2.log("foo", "bar")
)调用自定义函数时,代码现在无法找到,value
即使它仍然可以使用这些额外的参数.
关于令人费解的结果的更新:部分是因为来自@amon的回答,我意识到令人费解的结果没有出现因为我改变了参数的数量,而是因为我只是lift
在monad上重复调用ed方法(无论是或者参数的数量是否已经改变).因此,连续运行monad2.log()
两次将在第一次产生正确的值,但第二次将不确定.
问题
那么,为什么这段代码需要原型?或者,或者,如何消除原型导致value
可以访问某些时间而不是其他时间?
演示代码说明
代码的两个版本如下所示.原型使用代码(MONAD1
)与Crockford在他的视频中使用的代码相同,只是附加的自定义函数console.log
不是为了alert
让我可以在节点而不是在浏览器中使用它.非原型使用代码(MONAD2
)进行注释中指示的更改.输出显示在注释中.
原型使用代码
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"
Run Code Online (Sandbox Code Playgroud)
非原型使用代码
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. ???
Run Code Online (Sandbox Code Playgroud)
JSBin
我已在节点中使用此代码,但您可以在此jsbin中查看结果.Console.log
似乎在jsbin中的工作方式与终端中的节点完全相同,但它仍然显示了结果的同样令人费解的方面.(如果您只是单击控制台窗格中的"运行",则jsbin似乎不起作用.而是必须通过单击"输出"选项卡激活输出窗格,然后单击"使用js运行" "输出"窗格可在"控制台"窗格中查看结果.)
您必须明确区分特定类型的 monad 和实际包含值的 monad 实例。您的第二个示例是以我稍后将讨论的方式将两者混合在一起。
\n\n首先,该MONAD
函数构造一个新的 monad 类型。概念 \xe2\x80\x9cmonad\xe2\x80\x9d 本身并不是一种类型。相反,该函数创建一个具有类似 monad 行为的类型:
unit
操作将一个值包装在一个 monad 内。它是一种构造函数:monadInstance = MonadType(x)
。在哈斯克尔中:unit :: Monad m => a -> m a
.bind
操作将函数应用于 monad 实例中的值。该函数必须返回相同类型的 monad。然后绑定操作返回新的 monad: anotherMonadInstance = monadInstance.bind(f)
。在哈斯克尔中:bind :: Monad m => m a -> (a -> m b) -> m b
.您可以将MonadType
和unit()
操作视为或多或少相同的事情。我们创建单独原型的原因是我们不想从 \xe2\x80\x9cfunction\xe2\x80\x9d 类型继承随机包袱。此外,通过将其隐藏在 monad 类型构造函数中,我们可以保护它免受未经检查的访问 \xe2\x80\x93 只能lift
添加新方法。
操作lift
不是必需的,但非常方便。它允许对普通值(不是 monad 实例)起作用的函数应用于 monad 实例。通常,它会返回一个在 monad 级别运行的新函数:functionThatReturnsAMonadInstance = lift(ordinaryFunction)
。在哈斯克尔中:lift :: Monad m => (a -> b) -> (a -> m b)
. 但是哪种单子lift
应该返回呢?为了保留此上下文,每个提升的函数都绑定到特定的MonadType
. 注意:不仅仅是一个特定的monadInstance
!一旦一个函数被提升,我们就可以将它应用于同一类型的所有 monad。
我现在将重写代码以使这些术语更加清晰:
\n\nfunction CREATE_NEW_MONAD_TYPE() {\n var MonadType = Object.create(null);\n function unit (value) {\n var monadInstance = Object.create(MonadType);\n monadInstance.bind = function (func, ...args) {\n return func(value, ...args);\n }\n return monadInstance;\n }\n unit.lift = function (name, func) {\n MonadType[name] = function (...args) {\n return unit(this.bind(func, ...args));\n };\n return unit;\n };\n return unit;\n}\n\nvar MyMonadType = CREATE_NEW_MONAD_TYPE()\nMyMonadType.lift(\'log\', console.log); // adds MyMonadType(\xe2\x80\xa6).log(\xe2\x80\xa6)\n\nvar monadInstance = MyMonadType("Hello world.");\n\nmonadInstance.log(); // --> "Hello world."\nmonadInstance.log("foo", "bar"); // --> "Hello world. foo bar"\n
Run Code Online (Sandbox Code Playgroud)\n\n您的代码中发生的情况是您摆脱了monadInstance
. 相反,您可以bind
向MonadType
!添加一个操作。此bind
操作恰好引用了用 包裹的最后一个unit()
值。
现在请注意,提升函数的返回值被包装为带有 的 monad unit
。
monadInstance
(实际上是MonadType
)时,thenMonadType.bind()
指的是该"Hello World"
值。log()
函数。它接收 monad 中的值,这是用 包裹的最后一个值unit()
,即"Hello World"
。提升函数 ( ) 的返回值console.log
用 包裹起来unit()
。这个返回值为undefined
. 然后,您可以用引用该值的bind
新函数替换该函数。bind
undefined
log()
函数。它接收 monad 中的值,这是用 包裹的最后一个值unit()
,即undefined
。观察到的输出随之而来。 归档时间: |
|
查看次数: |
215 次 |
最近记录: |