Scala 如何在OOP语言中实现按需惰性评估,使其符合函数式编程范式?

Scala 如何在OOP语言中实现按需惰性评估,使其符合函数式编程范式?,scala,functional-programming,lazy-evaluation,Scala,Functional Programming,Lazy Evaluation,我在从面向对象思维转换到函数思维方面遇到了一些困难。我目前的问题是,我有一个不可变的、持久的数据结构,用于(比方说)构建URL-s: class UrlBuilder { public UrlBuilder withHost(String domain) { return new UrlBuilder(/*...*/); } public UrlBuilder withPort(Int port) { return new UrlBuilder(/*...*/);

我在从面向对象思维转换到函数思维方面遇到了一些困难。我目前的问题是,我有一个不可变的、持久的数据结构,用于(比方说)构建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;
  }
}
如果我需要线程安全性,我只需要使用双重检查锁定并完成它。但据我所知,这是对功能范式的反对,因为它引入了副作用(修改对象的内部状态)

我知道在Scala中有
lazy
关键字,它正是我所需要的:实现所谓的按需惰性。但是我如何用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之类的东西中实现这一点。正如我所提到的,我可以只使用锁定,但我正在寻找一种不必使用锁定的纯功能解决方案

我的印象是,函数式编程是线程安全的开箱即用,因此我觉得锁定就像一个丑陋的OOP解决方案。当然,我也会接受一个证明这是不可能的答案。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对这个问题的最佳答案。它是关于所谓瞬态数据结构的闭包实现。它们基本上是在持久数据结构的可变副本上操作的,但对外部世界来说是透明的(在后台使用锁定)

除了描述数据结构是如何工作的外,这篇文章本质上说,只要不能观察到变异,就可以进行变异

事实证明,这是一个哲学问题。这篇文章的引文总结了这一点:

如果一棵树倒在树林里,它会发出声音吗

如果一个纯函数为了产生一个不可变的返回值而改变一些本地数据,这样可以吗

-Rich Hickey,Clojure


在函数式编程中修改无法观察到的内部状态并不是错误的。我不明白为什么
lazy
在这里对您不起作用。你能解释一下为什么它不够吗?您是否在问如何用另一种语言(如Java)实现
lazy
?在这里,我也会小心使用您的一些语言:面向对象编程和函数式编程彼此并不冲突——Scala支持这两种范式!您可以在使用
lazy
时查看反编译的代码,以便更好地理解实现。请在此阅读更多相关信息:@Bergi您可能就在这里。如果有一种方法可以使用开箱即用的螺纹安全性来实现这一点就好了。你能指出一些引用来支持你的声明吗?@MaciejSz:Hm,也许“如果它没有改变任何可观察的东西,那么它就不是一个按定义的”?这不会改变以下代码将执行两次
build
,并且不会缓存值的事实:
val url=UrlBuilder.empty.withHost(…).withPort(…);url.build();url.build()
build()方法的实现是最重要的部分,您忽略了它。它可能只是一个懒惰的val,所有内容都将保持不变,并且只在问题是如何实现懒惰的val时才会计算。我对此投赞成票,因为这可能是唯一的解决方案,本·赖希在本书早些时候提到了这一点。当然,我会在这里使用双重检查锁定,而不仅仅是同步方法。或者,如果已经生成了某个值,可能不会同步访问器。someRXM的包装器仍然需要检查以防止竞争。不需要volatile或包装,这就足够了。一次只能有一个线程访问此方法。只有当其他方法获得某些值时,您才会有奇怪的行为。