在函数式编程中,为什么IO的join方法应该运行unsafePerformIO两次?

时间:2017-06-14 09:15:01

标签: javascript functional-programming monads

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两次以返回一个新的IO而不仅仅是return this.unsafePerformIO()

1 个答案:

答案 0 :(得分:3)

在我这样说之前没有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...早于出现比我们预期/期望的那样< - >现在将其与正确的IO.join实施进行比较 - 所有效果推迟到在最外面的IO上调用unsafePerformIO

再次

框,取消两次

通常,所有IO操作都会在延迟计算周围添加一个新框。对于join具体来说,我们仍然需要添加一个新的框,但操作是拆箱两次,所以我们仍然有效地从2级嵌套到1。

&#13;
&#13;
// 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())
&#13;
&#13;
&#13;

不仅仅是IO

可以说,join的这个盒子再次拆箱两次方法也适用于其他monad

&#13;
&#13;
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())
&#13;
&#13;
&#13;

在上面,看起来Maybe.join有时会返回一个Maybe,有时它只能返回盒装值。因为它不能保证返回一个Maybe,所以它更难依赖它的行为

现在,将其与下面的box-again-unbox-two方法进行比较

&#13;
&#13;
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())
&#13;
&#13;
&#13;

弱类型的JavaScript

在上面的示例中,我们的Maybe(Maybe(Number))转换为Maybe(Maybe(undefined)),这会导致强类型语言出错。但是,在JavaScript的情况下,在您尝试使用undefined实际期望5的地方之前,不会出现此类错误 - 这是一个不同类型的问题,但我个人赞成已知的codomain(返回类型)超过一个我必须稍后进行类型检查。

当然我们可以通过在连接本身内部进行类型检查来解决这个问题,但现在可能是不纯的并且可能在运行时抛出错误。

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写成一种幂等函数;如果可以,它将加入,否则它将自行返回 - 现在你得到保证的Maybe返回类型,没有运行时错误的可能性

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 ^ _ ^