总的来说,您不太常用协议、记录和自定义类型。使用普通地图和常规函数对于大多数应用程序领域的特定逻辑就足够了。不过,我将尝试解释在什么情况下您会想要使用它们。
TL;DR
当您需要实现自定义数据容器,需要底层的可变性和/或某种形式的数据封装时,可以使用 deftype。这主要用于类似数据结构或引用类型这样的非常原始的结构。
如果您有一个在运行时调用不同内容的函数,并且它执行的操作必须针对其被调用的内容特定。那么您想使用协议或多方法。如果您可以通过选择第一个参数的类型来确定要执行的操作,请使用协议,否则请使用多方法。
如果您有一个函数应根据地图的类型执行不同的事情。比如说,您需要将地图标记为表示某个东西,比如人、车、用户等。并且您有需要用类型更多的地图调用的函数,并且希望根据该类型选择执行的操作,请使用 defrecord。
如果您有一个函数需要根据它被调用的参数数量执行某些操作,请使用多参数。
最后,如果您发现自己正在编写许多以相同名称开始但以某种与所调用内容类型相关的辨别器结束的函数。比如:添加用户
、添加角色
、添加条目
,这可能表明您可以使用协议或多方法以及单个添加
函数来建模这种情况。
deftype
我将从 deftype 开始。您最不经常使用 deftype。deftype 允许您实现新的抽象数据类型(ADT)。它对于创建新的数据容器类型很有用。基本上,任何需要封装数据(通常是可变数据),并通过一个安全接口以任何合理的方式为该类型强制所有底层数据不变性的东西都可以使用它。
例如,如果您需要添加一个新的数据结构,比如实现一个双向链表,那么您将使用 deftype。
现在,你几乎不需要使用它,因为大多数有用的数据容器已经在核心、互操作性层或者是作为库为您实现。例如,Java已经提供了双链表:https://docs.oracle.com/javase/8/docs/api/java/util/LinkedList.html
为了更好地理解,请考虑deftype允许数据封装。通常情况下,在Clojure中您不需要数据封装,因为默认的数据结构和绑定是不可变的,因此将数据以只读形式提供给外部没有危险。但即便如此,对于实现数据容器本身,例如不可变数据结构或者各种引用类型(如Atom),您需要使用变异以实现空间和时间的有效实现。在这种情况下,您不应让外部可以随意访问数据,因为它们可能会轻易破坏所需的不变性。因此,您希望提供一个抽象接口,例如对于双链表,您可能会有添加第一个元素、添加最后一个元素、删除、下一个、前一个等操作。所以,一遍又一遍地,deftype对于向语言添加任何形式的数据容器都是有用的,几乎不需要自己去做。
defrecord
当您需要创建一个自定义类型来为某些Map添加语义意义时,您会转向使用defrecord。记录实际上只是一个Map,只是将它替换为记录类型名称。因此,Map不会是类型为Map,而是类型为记录名称。
如果您想要一个Person类型的Map,您会使用defrecord。
这只有在您需要一段代码在运行时根据类型执行不同的操作时才真正有用。所以如果有一段代码将被不同类型的Map接收,并且希望它对每种类型都执行不同的操作,同时还想获得更好的性能。
我还要说的是,实际上有很多种方式提供这种动态功能。其中一种是用原生类型,由记录和协议提供。另一种是手动建模类型并使用defmulti。后者更强大,因为您可以用任何您想代表的内容来表示类型,类型甚至可以由数据的结构或值推断出来。另一方面,它会慢一些。
defrecord只能允许函数根据它们第一个参数的类型执行不同的事情,而记录的结构和值不能决定其类型。这通常被称为命名类型。您的记录是某种类型,因为您明确地给出了这种类型的名字。另一方面,defmulti允许使用鸭式类型,即一个Map因其固有结构或包含的值而具有某种类型。
defprotocol
当您想要函数根据它们第一个参数的原生类型执行不同的事情时,请使用defprotocol。比如一个会被不同的记录调用,并根据每个不同的记录类型执行不同操作的功能。
defmulti
正如我在简要介绍记录时解释的那样。如果您希望一个函数不仅根据第一个参数的类型执行不同的操作,而且还因为其他参数的类型或任何参数中数据结构,或者任何参数的值而执行不同的操作,那么您将使用defmulti而不是defprotocol。
多参数函数
此外,如果您只想根据被调用的函数的参数数量执行不同的操作,您可以直接使用多参数函数。