通过在函数中包装参数来延迟评估?

Delaying evaluation by wrapping parameter in a function?

在 Mostly Adequate Guide to Functional Programming Chapter 8 中,他们定义了一个新的 class、IO,其定义如下:

class IO {
  static of(x) {
    return new IO(() => x);
  }

  constructor(fn) {
    this.$value = fn;
  }

  map(fn) {
    return new IO(compose(fn, this.$value));
  }

  inspect() {
    return `IO(${inspect(this.$value)})`;
  }
}

作者解释:

IO delays the impure action by capturing it in a function wrapper. As such, we think of IO as containing the return value of the wrapped action and not the wrapper itself. This is apparent in the of function: we have an IO(x), the IO(() => x) is just necessary to avoid evaluation.

但我对 .of() 方法如何延迟评估感到困惑。例如,从本节开头开始定义,

// getFromStorage :: String -> (_ -> String)
const getFromStorage = key => () => localStorage[key];

例如,如果我尝试创建一个新的 IO 对象,例如 IO.of(localStorage[42]),这根本不会延迟计算。 localStorage[42] 的值将立即求值(假设它求值为 "foo"),然后新的 IO 对象将使用 { $value: () => "foo" }.

创建

我理解如何像 new IO(key => () => localStorage[key]) 那样直接 调用构造函数 会延迟求值,但我不明白作者使用 [=17= 是什么意思] 方法又是怎么说的"avoids evaluation"。此外,作者在 IO 的任何示例中都没有使用 .of(),而是直接调用构造函数。

为了使 IO 成为单子(根据那本书的单子概念),它需要一个 .of 方法来将任意值包装在 IO 中。 IO.of 这样做。由于本书对 IOs 的实现的本质是它们带有一个可以在稍后计算的函数,因此 .of 方法将传递的值包装在一个函数中。

IO.of(5) 创建一个 IO 的实例来包装值 5。就这些。 .of 并没有真正延迟效果。

关于评论中的问题:

Then what does the author mean by saying "This is apparent in the of function: we have an IO(x), the IO(() => x) is just necessary to avoid evaluation."

我认为理解该评论所需的信息是它前面的内容:

We don't think of its $value as a function, however - that is an implementation detail and we best ignore it. ... As such, we think of IO as containing the return value of the wrapped action and not the wrapper itself.

他的观点似乎是,从概念上讲,IO 的 "value" 是包含的函数最终求值的值,但为了实现延迟求值,它在内部存储了一个未评估的函数,直到 IO 有必要解析为一个值。

因此您可以通过调用 IO.of(5) 为值 5 创建一个 IO,但在内部,它包含一个计算结果为 5 的函数,因此在稍后可以将此函数计算为一个值。

如果您想创建一个 IO 实际上会延迟对某些不纯效果的评估,请使用构造函数并向其传递一个函数。

我喜欢将 IO.of(x) 视为不纯行为的起点。

例如,您可能想要读取 DOM 元素的内容,而 x 将是它的 ID:(in "pseudo code")

const readInput = id =>
  IO
    .of(id)
    .map(id => document.getElementById(id))
    .map(el => el.textContent)

readInput('#login');
// combine with other monads for example