5

在 Clojure Programming (OReilly) 中有一个示例,其中 java.io.BufferedWriter 和 java.io.Printwriter 都放在一个代理中(每个代理一个)。然后将这些写入代理操作内部。这本书说在代理动作中执行 io 是安全的。据我了解,代理操作中的所有副作用操作都可以。这是因为只有在提交成功时才会运行提交中的代理操作。其他代理操作中的代理操作仅在外部代理操作成功完成后运行。一般来说,代理动作保证是连续应用的。

Clojure文档说:“代理的状态本身应该是不可变的……”。

据我了解,atoms 和 refs 必须保持不可变值的原因是 clojure 可以回滚并重试多次提交。

我不明白的是:

1:如果 Clojure 确保代理操作只运行一次,为什么代理值必须是不可变的。(例如,如果我在代理中保存一个 java 数组,并在代理操作中添加到它,这应该没问题,因为该操作只会运行一次。这与向 BufferedWriter 添加行非常相似)

2:java.io.BufferedWriter 被认为是不可变的吗?我知道您可以对一个有稳定的引用,但是如果代理操作正在对其执行 io,它是否仍应被视为不可变的?

3:如果BufferedWriter被认为是不可变的,如何判断其他类似的java类是否不可变?

4

2 回答 2

4

照我看来:

代理人持有的价值应该是“有效不变的”(从 JCIP 借用的术语),因为它们在概念上应该始终与自己相等。

这意味着,如果我.clone()是一个对象并比较两个副本,original.equals(copy)无论我做什么(以及何时),都应该是真的。

从这个意义上说,一个充满getter/setter的典型Employee类的实例不能保证与自己相等,面对可变性:equals()将被定义为逐字段比较,因此测试可能会失败。

但是,BufferedWriter 并不代表一个值——它的相等性是根据内存中完全相同的对象来定义的。因此,它具有“健全”的可变性——与 Employee 的不同——这使得它易于将其包装在代理中。

我相信你是对的,从 STM 的角度来看,代理价值的可变性不会造成太大的伤害。但它会打破 Clojure 的时间模型,在该模型中你“无法改变过去”等。

关于确定 Java 类是否是不可变的:不深入实现是不可能的。不过,您不必太在意这一点。

我会在 Java 领域对类型进行以下分类:

  • (严重)表示值的可变对象 -Employee等。永远不要将它们包装在 Clojure 引用类型中。

  • 表示值的不可变对象 - 它们的不变性反映在文档或命名约定(“EmployeeBuilder”)中。可以安全地包装在任何 Clojure 参考中。

  • 非托管集合类型 - ArrayList 等。除了互操作目的外,请避免使用。

  • 托管引用/集合类型 - AtomicReference,阻塞队列......它们与 Clojure 配合得很好,但怀疑将它们包装在 Clojure 引用中。

  • 'IO' 类型 - BufferedWriter、Swing 的东西......你不关心它们的可变性,因为它们根本不代表值 - 你只想要它们的副作用。在代理中保护它们以保证访问序列化可能是有意义的。

于 2013-06-07T11:22:25.820 回答
2

代理值应该是不可变的,因为有人可以这样做:

 (def my-agent (agent (BufferedWriter.)))
 (.write @my-agent "Hello world")

这基本上是在不通过代理控制机制的情况下修改代理值(在这种情况下是作者)。

是的,BufferedWriter是可变的,因为通过写入它你可以改变它的内部状态。它就像一个指针或引用,而不是一个value.

于 2013-06-07T12:03:21.087 回答