如何在 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
我在从 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 解决方案。但当然我也会接受一个证明这是不可能的答案。
这个怎么样:
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