In DrBoolean's Gitbook,也许有一些示例可以解释monad:

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()

最佳答案

没有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个。



// 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方法也适用于其他monad



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有时会返回Maybe,而在其他时候,它可能只是返回装箱的值。因为不能保证会返回Maybe,所以很难依靠它的行为

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



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而言,直到您尝试实际使用undefined5时,此类错误才会显现出来–这是另一种问题,但我个人更喜欢已知的共域(返回类型),我以后必须进行类型检查。

当然,我们可以通过在连接本身内部进行类型检查来解决此问题,但是现在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。如果可以,它将加入,否则它将返回自身–现在,您将获得保证的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 ^ _ ^

09-07 20:56