clojure 中比使用实例更惯用的模式?按类型分派

More idiomatic pattern in clojure than using instance? to dispatch on type

我尝试实现以下模式:我有一个 AST 和 2 个生成器,一个用于 C#,一个用于 VB。生成器将提供的 AST 作为输入并生成所选语言的源代码。首先我为生成器定义一个抽象:

(defprotocol Generator
  (generate-code [this ast]))

然后我给出一个实现,一个是C#的,一个是VB的(我只给出C#的实现,因为VB的几乎一模一样):

(defn ^:private gen-csharp-class [ast]
  (str "class " (:id ast) " {  }"))

(defn ^:private gen-csharp [ast]
  (cond
    (instance? AstClass ast) (gen-csharp-class ast)))

(defrecord AGenerator []
  Generator
  (generate-code [this ast] (gen-csharp ast)))

我不喜欢在 gen-csharp 函数中使用 instance?。在 Clojure 中是否有更惯用的方式来编写这种类型的调度?

你可以反转抽象:

(defprotocol CodeGenerator
  (generate-class [this ast])
  ;; + other generator methods
  )

(defrecord VBGenerator []
  CodeGenerator
  (generate-class [this ast] (gn-vb-class ast)))

(defrecord CSharpGenerator []
  CodeGenerator
  (generate-class [this ast] (gn-chsharp-class ast)))

然后只写一次 instance? 调用:

(defn gen-code [generator ast]
  (cond (instance? AstClass ast) (generate-class generator ast)
         ;; + other generator cases...
         ))

或者你可以在 gen-class 的第二个参数上使用 multimethods。每个多方法定义都会从 CodeGenerator:

调用正确的方法
(defmulti gen-class (fn [_ ast] (type ast)))

(defmethod gen-class AstClass [generator ast]
  (generate-class generator ast))

或者,您也可以只使用无协议的多方法。

(defmulti gen-code (fn [language ast] [language (type ast)]))

此处第一个参数将是表示目标语言的关键字,但是,您也可以使用类型。

;; csharp definitions
(defmethod gen-code [:csharp AstClass] [_ ast]
  (gen-chsharp-class ast))

;; vb definitions
(defmethod gen-code [:vb AstClass] [_ ast]
  (gen-vb-class vb))

(defmethod gen-code [:vb AstNumber] [_ ast]
  ...)
...

然后用节点调用gen-code:

(gen-code :vb (new AstClass ...))