为什么 Clojure 有5种方法来定义一个类,而不是只有一种?

Clojure 具有 gen-class、 reify、 xy 以及 deftype 和 defrecord 来定义新的类似类的数据类型。对于一种重视语法简单性并厌恶不必要复杂性的语言来说,这似乎是一种偏差。 有人能解释一下为什么会这样吗? Common Lisp 风格的 defclass 就足够了吗?

13417 次浏览

This is a mix of three different factors:

  1. The particular type system of the jvm
  2. The need for slightly different semantics for different use cases when defining types
  3. The fact that some of these were developed earlier, and some later, as the language has evolved.

So first, let's consider what these do. deftype and gen-class are similar in that they both define a named class for ahead-of-time compilation. Gen-class came first, followed by deftype in clojure 1.2. Deftype is preferred, and has better performance characteristics, but is more restrictive. A deftype class can conform to an interface, but cannot inherit from another class.

Reify and proxy are both used to dynamically create an instance of an anonymous class at runtime. Proxy came first, reify came along with deftype and defrecord in clojure 1.2. Reify is preferred, just as deftype is, where the semantics are not too restrictive.

That leaves the question of why both deftype and defrecord, since they appeared at the same time, and have a similar role. For most purposes, we will want to use defrecord: it has all the various clojure goodness that we know and love, sequability and so forth. Deftype is intended for use as a low level building block for the implementation of other datastructures. It doesn't include the regular clojure interfaces, but it does have the option of mutable fields (though this isn't the default).

For further reading check out:

The clojure.org datatypes page

The google group thread where deftype and reify were introduced

The short answer is that they all have different and useful purposes. The complexity is due to the need to interoperate effectively with different features of the underlying JVM.

If you don't need any Java interop then 99% of the time you are best off sticking with either defrecord or a simple Clojure map.

  • Use defrecord if you want to use protocols
  • Otherwise a regular Clojure map is probably simplest and most understandable

If your needs are more complex, then the following flowchart is a great tool for explaining why you would choose one of these options over the others:

http://cemerick.com/2011/07/05/flowchart-for-choosing-the-right-clojure-type-definition-form/

Flowchart for choosing the right clojure type definition form