如何在 OOP 语言中实现 by-need 惰性求值,使其符合函数式编程范式?

How to implement the by-need lazy evaluation in an OOP language, so that it complies with functional programing paradigm?

我在从 OOP 思维切换到函数式思维时遇到了一些麻烦。我当前的问题是我有一个不可变的、持久的数据结构,它用于(比方说)构建 URL-s:

class UrlBuilder {

  public UrlBuilder withHost(String domain) {
    return new UrlBuilder(/*...*/);
  }

  public UrlBuilder withPort(Int port) {
    return new UrlBuilder(/*...*/);
  }

  // ...

  public String build() {
    // ...
  }
}

延迟计算字符串的 build() 方法非常昂贵,所以我想缓存结果。

在 OOP 中这没有问题,因为我可以这样做:

class UrlBuilder {
  private String url;

  // ...

  public String build() {
    if (null == this.url) {
      this.url = doExpensiveEvaluation();
    }
    return this.url;
  }
}

如果我需要 thread-safety 我会使用 doubly-checked 锁定并完成它。但据我所知,这违反了功能范式,因为它引入了副作用(修改 object 的内部状态)。

我知道在 Scala 中有一个 lazy 关键字,它正是我需要的:实现所谓的 by-need 惰性。但是我怎样才能用 OOP 语言做同样的事情呢?我其实很好奇他们是如何在 Scala 中实现这个的。

我试图将缓存结果的责任推给 UrlBuilder 的消费者,但这在消费者方面造成了同样的问题:

class Consumer {
  private UrlBuilder urlBuilder;
  private String url;
  // ...
  public String getUrl() {
    if (null == this.url) {
      this.url = urlBuilder.build(); // same as before!
    }
    return this.url;
  }
}

因此我在标题中提出问题。

编辑:明确一点:我问的是 Scala 以外的 OOP 语言的实现。它可能是 Java 或 C#,但我也想知道如何使用 JavaScript 之类的东西来做到这一点。正如我提到的,我可以只使用锁定,但我一直在寻找 pure-functional 无需使用锁的解决方案。

我的印象是函数式编程 thread-safe 开箱即用,因此锁定对我来说就像一个丑陋的 OOP 解决方案。但当然我也会接受一个证明这是不可能的答案。 by Ben Reich 几乎说明了一切:如果 Scala 开发人员不能在没有锁定的情况下做到这一点,那么我可能会死于尝试。

这个怎么样:

object UrlBuilder{
    def empty = new InnerBuilder("")

    class InnerBuilder(...){
        def withHost(host: String) = new InnerBuilder(...)
        def withPort(port: Int) = new InnerBuilder(...)
        def build(): String = ...
    }

这样你就没有任何可变状态 }

并像这样使用它:

UrlBuilder.empty
          .withHost(...)
          .withPort(...)
          .build()

我们正在谈论 java 不是吗?为什么不同步它?

class LazyClass 
{

    Integer someValue = null;
    public synchronized Integer someReallyExpensiveMethod() {
        if (someValue == null)
        {
            someValue = 1 + 2 + 3; // .. + 32 + .. this takes a long time
        }
        return someValue;
    }

}

我找到了 Rich Hickey 对这个问题 in this article 的最佳答案。它是关于所谓的 transient 数据结构的 Closure 实现。它们本质上是对持久数据结构的可变副本进行操作,但对外界透明地执行此操作(在后台使用锁定)。

除了描述数据结构的工作原理之外,这篇文章基本上指出,只要无法观察到突变,就可以进行突变。

事实证明,这在某种程度上是一个哲学问题。这篇文章的引用总结得很好:

If a tree falls in the woods, does it make a sound?

If a pure function mutates some local data in order to produce an immutable return value, is that ok?

— Rich Hickey, Clojure