在函数式编程中,为什么IO的join方法要运行两次unsafePerformIO?
In functional programming, why IO's join method should run the unsafePerformIO twice?
In DrBoolean's Gitbook,有几个例子解释了monad,对于Maybe:
Maybe.prototype.join = function() {
return this.isNothing() ? Maybe.of(null) : this.__value;
}
对于 IO:
IO.prototype.join = function() {
var thiz = this;
return new IO(function() {
return thiz.unsafePerformIO().unsafePerformIO();
});
};
我想知道为什么 IO 应该 运行 unsafePerformIO 两次 return 一个新的 IO 而不是 return this.unsafePerformIO()
?
除非我说,否则不要 IO
在 IO 的情况下,重要的是我们在需要之前不执行任何 IO – 在下面的示例中,请特别注意输出行的顺序
// IO
const IO = function (f) {
this.unsafePerformIO = f
}
IO.of = function (x) {
return new IO(() => x)
}
IO.prototype.join = function () {
return this.unsafePerformIO()
}
// your main program
const main = function (m) {
console.log('you should not see anything above this line')
console.log('program result is:', m.unsafePerformIO())
}
// IO (IO (something))
const m = new IO(() => {
console.log('joining...')
return IO.of(5)
})
// run it
main(m.join())
上面,joining...
比我们 expected/desired 出现 早 – 现在将其与正确的 IO.join
实现进行比较 – 所有 效果都被推迟,直到 unsafePerformIO
在最外层的 IO 上被调用。
再次装箱,两次拆箱
通常,所有 IO 操作都会在延迟计算周围添加一个新框。 join
具体来说,我们还是要添加一个新的盒子,但是这个操作是拆箱两次,所以我们仍然有效地从 2 层嵌套下降到 1 层。
// IO
const IO = function (f) {
this.unsafePerformIO = f
}
IO.of = function (x) {
return new IO(() => x)
}
IO.prototype.join = function () {
return new IO(() => this.unsafePerformIO().unsafePerformIO())
}
// your main program
const main = function (m) {
console.log('you should not see anything above this line')
console.log('program result is:', m.unsafePerformIO())
}
// IO (IO (something))
const m = new IO(() => {
console.log('joining...')
return IO.of(5)
})
// run it
main(m.join())
不只是IO
这种用于 join
的 box-again-unbox-twice 方法也适用于其他 monads 是有争议的
function Maybe (x) {
this.value = x
}
Maybe.of = function (x) {
return new Maybe(x)
}
Maybe.prototype.join = function () {
// assumes that this.value is a Maybe
// but what if it's not?
return this.value;
}
Maybe.prototype.toString = function () {
return `Maybe(${this.value})`
}
const m = Maybe.of(Maybe.of(5))
console.log("m == %s", m)
console.log("m.join() == %s", m.join())
// hmm... now it seems `.join` can return a non-Maybe??
console.log("m.join().join() == %s", m.join().join())
在上面,Maybe.join
有时会 return 一个 Maybe,而其他时候它可能只是 return 盒装值。因为它不能保证 Maybe 是 returned,所以很难依赖它的行为
现在,将其与下面的再次装箱-拆箱-两次方法进行比较
function Maybe (x) {
this.value = x
}
Maybe.of = function (x) {
return new Maybe(x)
}
Maybe.prototype.join = function () {
// box again, unbox twice
// guaranteed to return a Maybe
return Maybe.of(this.value.value)
}
Maybe.prototype.toString = function () {
return `Maybe(${this.value})`
}
const m = Maybe.of(Maybe.of(5))
console.log("m == %s", m)
// this still works as intended
console.log("m.join() == %s", m.join())
// here join still returns a Maybe as expected,
// but the inner value `undefined` reveals a different kind of problem
console.log("m.join().join() == %s", m.join().join())
弱类型JavaScript
在上面的示例中,我们的 Maybe(Maybe(Number))
转换为 Maybe(Maybe(undefined))
,这将导致强类型语言出错。但是,在 JavaScript 的情况下,在您尝试使用 undefined
之前不会出现明显的错误,而您实际上期望的是 5
– 这是一种不同类型的问题,但是我个人更喜欢已知的密码域(return 类型),而不是我稍后必须进行类型检查的密码域。
当然,我们可以通过在 join 内部进行类型检查来解决这个问题,但现在 Maybe 是不纯的,可能会在运行时抛出错误。
Maybe.prototype.join = function () {
if (this.value instanceof Maybe)
return this.value
else
throw TypeError ('non-Maybe cannot be joined')
}
遗憾的是,这就是 JavaScript 在函数式编程的某些方面出现问题的地方。此处 Maybe.join
的每个实施都需要权衡取舍,因此您必须选择最适合您的方法。
某种幂等性
也许您甚至可以将 Maybe.join
写成某种幂等函数;如果可以的话,它会加入,否则它只会 return 本身——现在你得到了保证的 Maybe
return 类型,并且不可能出现运行时错误
Maybe.prototype.join = function () {
if (this.value instanceof Maybe)
return this.value
else
return this
}
但是,下面的程序现在已通过此实现验证
// should this be allowed?
Maybe.of(Maybe.of(5)).join().join().join().join().join() // => Maybe(5)
权衡,权衡,权衡。选择你的毒药还是选择 PureScript ^_^
In DrBoolean's Gitbook,有几个例子解释了monad,对于Maybe:
Maybe.prototype.join = function() {
return this.isNothing() ? Maybe.of(null) : this.__value;
}
对于 IO:
IO.prototype.join = function() {
var thiz = this;
return new IO(function() {
return thiz.unsafePerformIO().unsafePerformIO();
});
};
我想知道为什么 IO 应该 运行 unsafePerformIO 两次 return 一个新的 IO 而不是 return this.unsafePerformIO()
?
除非我说,否则不要 IO
在 IO 的情况下,重要的是我们在需要之前不执行任何 IO – 在下面的示例中,请特别注意输出行的顺序
// IO
const IO = function (f) {
this.unsafePerformIO = f
}
IO.of = function (x) {
return new IO(() => x)
}
IO.prototype.join = function () {
return this.unsafePerformIO()
}
// your main program
const main = function (m) {
console.log('you should not see anything above this line')
console.log('program result is:', m.unsafePerformIO())
}
// IO (IO (something))
const m = new IO(() => {
console.log('joining...')
return IO.of(5)
})
// run it
main(m.join())
上面,joining...
比我们 expected/desired 出现 早 – 现在将其与正确的 IO.join
实现进行比较 – 所有 效果都被推迟,直到 unsafePerformIO
在最外层的 IO 上被调用。
再次装箱,两次拆箱
通常,所有 IO 操作都会在延迟计算周围添加一个新框。 join
具体来说,我们还是要添加一个新的盒子,但是这个操作是拆箱两次,所以我们仍然有效地从 2 层嵌套下降到 1 层。
// IO
const IO = function (f) {
this.unsafePerformIO = f
}
IO.of = function (x) {
return new IO(() => x)
}
IO.prototype.join = function () {
return new IO(() => this.unsafePerformIO().unsafePerformIO())
}
// your main program
const main = function (m) {
console.log('you should not see anything above this line')
console.log('program result is:', m.unsafePerformIO())
}
// IO (IO (something))
const m = new IO(() => {
console.log('joining...')
return IO.of(5)
})
// run it
main(m.join())
不只是IO
这种用于 join
的 box-again-unbox-twice 方法也适用于其他 monads 是有争议的
function Maybe (x) {
this.value = x
}
Maybe.of = function (x) {
return new Maybe(x)
}
Maybe.prototype.join = function () {
// assumes that this.value is a Maybe
// but what if it's not?
return this.value;
}
Maybe.prototype.toString = function () {
return `Maybe(${this.value})`
}
const m = Maybe.of(Maybe.of(5))
console.log("m == %s", m)
console.log("m.join() == %s", m.join())
// hmm... now it seems `.join` can return a non-Maybe??
console.log("m.join().join() == %s", m.join().join())
在上面,Maybe.join
有时会 return 一个 Maybe,而其他时候它可能只是 return 盒装值。因为它不能保证 Maybe 是 returned,所以很难依赖它的行为
现在,将其与下面的再次装箱-拆箱-两次方法进行比较
function Maybe (x) {
this.value = x
}
Maybe.of = function (x) {
return new Maybe(x)
}
Maybe.prototype.join = function () {
// box again, unbox twice
// guaranteed to return a Maybe
return Maybe.of(this.value.value)
}
Maybe.prototype.toString = function () {
return `Maybe(${this.value})`
}
const m = Maybe.of(Maybe.of(5))
console.log("m == %s", m)
// this still works as intended
console.log("m.join() == %s", m.join())
// here join still returns a Maybe as expected,
// but the inner value `undefined` reveals a different kind of problem
console.log("m.join().join() == %s", m.join().join())
弱类型JavaScript
在上面的示例中,我们的 Maybe(Maybe(Number))
转换为 Maybe(Maybe(undefined))
,这将导致强类型语言出错。但是,在 JavaScript 的情况下,在您尝试使用 undefined
之前不会出现明显的错误,而您实际上期望的是 5
– 这是一种不同类型的问题,但是我个人更喜欢已知的密码域(return 类型),而不是我稍后必须进行类型检查的密码域。
当然,我们可以通过在 join 内部进行类型检查来解决这个问题,但现在 Maybe 是不纯的,可能会在运行时抛出错误。
Maybe.prototype.join = function () {
if (this.value instanceof Maybe)
return this.value
else
throw TypeError ('non-Maybe cannot be joined')
}
遗憾的是,这就是 JavaScript 在函数式编程的某些方面出现问题的地方。此处 Maybe.join
的每个实施都需要权衡取舍,因此您必须选择最适合您的方法。
某种幂等性
也许您甚至可以将 Maybe.join
写成某种幂等函数;如果可以的话,它会加入,否则它只会 return 本身——现在你得到了保证的 Maybe
return 类型,并且不可能出现运行时错误
Maybe.prototype.join = function () {
if (this.value instanceof Maybe)
return this.value
else
return this
}
但是,下面的程序现在已通过此实现验证
// should this be allowed?
Maybe.of(Maybe.of(5)).join().join().join().join().join() // => Maybe(5)
权衡,权衡,权衡。选择你的毒药还是选择 PureScript ^_^