Clojure中的reify是什么?

35
我不明白Clojure中“reify”函数的用法。它在Clojure中用于什么?
你能提供一些示例吗?

在文档页面上有示例:https://clojuredocs.org/clojure.core/reify - OlegTheCat
2个回答

101

reify 是对于 defrecord,就像 fn 对于 defn 一样。

简单来说,协议是数据类型应该支持的函数列表,记录是数据类型,而 reification 则是匿名数据类型。

也许这有点啰嗦,但是如果不了解协议和类型/记录,就无法具体理解 reify:协议是一种使用相同名称的函数(例如 conj),但在给定不同参数时实际上会以不同方式工作的方法 ((conj [:b :c] :a) => [:b :c :a],但是 (conj '(:b :c) :a) => (:a :b :c))。 记录类似于对象或类型(但它们的行为类似于映射,使它们更棒)。

更基本地,目标是解决“表达式问题”,即具有能够 轻松添加新类型数据以与现有函数一起使用,并能够轻松地添加新函数以与现有数据无缝配合工作的能力

因此,有一天你对自己说:“自己,你应该学习成为一只鸭子意味着什么!” 于是你写了一个协议:

(defprotocol Quacks
  (quack [_] "should say something in ducky fashion"))

但这一切都太抽象了,所以你要“实际化”它:

(def donald (reify Quacks
                   (quack [_] "Quacks and says I'm Donald")))

现在终于可以体验你的创作了:

(quack donald) => "Quacks and says I'm Donald"

然后你想起了达菲鸭:

(def daffy (reify Quacks
                  (quack [_] (str "Quacks and says I'm Daffy"))))

(quack daffy) => "Quacks and says I'm Daffy"

但是当你想起Huey时,你意识到了自己的错误,并以可重复使用的方式定义了鸭子:

(defrecord Duck [name]
  Quacks
  (quack [_] (str "Quacks and says I'm " name)))

创建new鸭子(有几种方法可以做到):

(def huey (->Duck "Huey"))
(def duey (Duck. "Duey"))
(def louie (new Duck "Louie"))

(quack huey) => "Quacks and says I'm Huey"

记住,记录就像地图一样(感谢协议!):

(:name huey) => "Huey"

但是你记得鸭子必须要 嘎嘎叫 走路,所以你写了另一个协议:

(defprotocol Walks
  (walk [_] "should walk like a duck"))

并扩展“duck”的定义

(extend-type Duck
  Walks
  (walk [_] "waddle waddle"))

(walk louie) => "waddle waddle"

现在我们可以扩展其他类型以实现相同的协议(教相同的函数如何与其他事物一起工作):

所以让我们假设我们也想让程序员嘎嘎叫 :-)

(defrecord Programmer [] Quacks
  (quack [_] "Monads are simply monoids in a category of endofunctors..."))

(quack (Programmer.)) => "Monads are simply monoids in a category of endofunctors..."

我推荐这篇解释协议的文章,以及这篇关于reify的解释和《Clojure for the Brave and True》中关于协议的章节。

免责声明:这只是为了初步理解协议,而不是如何使用它们的最佳实践。 “嘘!我回答这个问题主要是为了教自己,因为直到昨天我从未真正编写过自己的协议/接口!”

因此,虽然我希望它可以增强其他人的学习,但我非常欢迎批评或编辑建议!


3
我喜欢你的回答!既有实际解释又有幽默。正如费曼曾经说过的那样,试图向他人解释某事是自己学习的最佳途径。 - Wojciech Gac

29

reify 宏允许创建一个匿名类,该类扩展 java.lang.Object 类和/或实现指定的接口/协议。 API 文档 没有清楚地描述其目的,而是提供了该宏执行的技术细节。Java 互操作文档 提供了对此目的的简要描述:

从 Clojure 1.2 开始,reify 还可用于实现接口。

数据类型文档中可以找到更多信息,其中提供了非常详细的说明,说明了它是如何工作并与 proxy 进行比较:

虽然 deftype 和 defrecord 定义了命名类型,但 reify 定义了匿名类型并创建了该类型的实例。使用情况是当您需要一次性实现一个或多个协议或接口,并希望利用本地上下文时。在这方面,它类似于 Java 中的 proxy 或匿名内部类。

reify 的方法体是词法闭包,并且可以引用周围的局部范围。reify 与 proxy 的不同之处在于:

仅支持协议或接口,不支持具体的超类。方法体是结果类的真实方法,而不是外部函数。对实例上的方法的调用是直接的,而不是使用 map 查找。不支持动态交换方法映射中的方法。结果比 proxy 更好的性能,在构建和调用方面都是如此。只要其约束条件不是禁止性的,reify 在所有情况下都优于 proxy。


您无法使用 reify 扩展除 Object 以外的类,因此您的第一句话略有不正确。 - Nathan Davis
@NathanDavis 是的,你说得对,我已经修正了我的回答。 - Piotrek Bzdyl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接