【问题标题】:Delaying evaluation by wrapping parameter in a function?通过将参数包装在函数中来延迟评估?
【发布时间】:2020-04-19 20:16:24
【问题描述】:

在函数式编程的最充分指南Chapter 8 中,他们定义了一个新类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 通过在函数包装器中捕获不纯操作来延迟它。作为 这样,我们认为IO 包含被包装的返回值 动作而不是包装器本身。这在 功能:我们有一个IO(x)IO(() => x) 只是为了避免 评估。

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

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

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

我了解像new IO(key => () => localStorage[key]) 这样直接 调用构造函数如何延迟评估,但我不明白作者使用.of() 方法的含义以及如何“避免评估” .另外,作者在IO的任何示例中都没有使用.of(),而是直接调用构造函数。

【问题讨论】:

  • For monads of 表示将纯值放入相应 monad 的上下文中,在这种情况下为 IO(更好的是 Defer)。这意味着没有什么可以推迟了。当您将其放入上下文时,该值已被评估。这样做仍然是明智的,因为您现在可以在 IO monad 中使用这个纯值。
  • @bob 嗯,这很有道理。那么作者所说的“这在of 函数中很明显:我们有一个IO(x)IO(() => x) 只是为了避免评估所必需的”是什么意思。我们不再避免使用这个纯值进行评估,因为正如你所说,它已经被评估了。
  • 我认为Brian 只是试图提出另一点,即我们不应该将IO 视为一个无效函数。在这个特定的点上,要遵循他的想法有点困难。
  • 这实际上是一个很好的例子,为什么我们需要 of 作为 monad API 的一部分,而不仅仅是相应的构造函数。它们可能会有所不同,例如 IO

标签: javascript functional-programming monads


【解决方案1】:

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

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

关于您在 cmets 中的问题:

那么作者所说的“这在of 函数中很明显:我们有一个 IO(x),IO(() => x) 只是避免评估所必需的”,这是什么意思。

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

然而,我们不认为它的 $value 是一个函数——这是一个实现细节,我们最好忽略它。 ...因此,我们认为 IO 包含被包装操作的返回值,而不是包装器本身。

他的观点似乎是,从概念上讲,IO 的“值”是包含的函数最终评估为的值,但为了实现延迟评估,在内部,它存储了一个未评估的函数,直到如下时间IO 必须解析为一个值。

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

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

【讨论】:

  • 这很有意义,谢谢!所以澄清一下,关键区别在于.of 的评估确实没有延迟,因为传递给of 的任何内容都会立即评估并存储。但是,IO API 的一个关键部分是它的内部值是一个可以评估的函数。因此,为了实现这一点,我们只需将任何内容包装在一个函数中,以允许我们对 IO 类型的任何对象执行 ioObject.$value()?
  • @rb612 是的,没错。据我了解,各种 monad 类型上的 .of 方法很少打算直接使用,但存在以便您可以创建适用于任何 monad 类型的抽象(例如,flatMap 函数同样适用于IOMaybeList 或任何其他类型的单子)。 .of 方法的存在使这成为可能,并且是 monad 概念的基础。
  • @JLRishe 是的,of 具有比 ListMaybe 等特定数据构造函数更通用的类型(a => m a,其中 m 具有 monad 约束),因此它可以用于编写适用于任何单子的通用单子代码。 map 将纯函数提升到函子的上下文中并将其应用到内部。 of 只是将纯值提升到应用程序的上下文中。我写的是 applicative 而不是 monad,因为 of 是 applicative functor interface monads 继承自的一部分。
【解决方案2】:

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

例如,您可能想要读取 DOM 元素的内容,x 就是它的 id:(在“伪代码”中)

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

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

【讨论】:

    猜你喜欢
    • 2017-08-24
    • 1970-01-01
    • 1970-01-01
    • 2019-06-19
    • 2010-11-26
    • 1970-01-01
    • 2015-06-01
    • 2016-08-14
    • 1970-01-01
    相关资源
    最近更新 更多