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

Viewed 82

I have the following pattern that I am trying to implement: I have an AST and 2 Generators, one for C# and one for VB. A generator takes the provided AST as input and generates source code in the selected language. First I define an abstraction for a generator:

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

Then I give an implementation, one for C# and one for VB (I will just give the C# implementation because the VB one is almost exactly the same):

(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)))

I don't like the use of instance? in the gen-csharp function. Is there a more idiomatic way of writing this type of dispatch in Clojure?

2 Answers

You could reverse the abstraction:

(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)))

and then write the instance? calls only once:

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

or you could use multimethods on the second argument for gen-class. Each multimethod definition would call the correct method from CodeGenerator:

(defmulti gen-class (fn [_ ast] (type ast)))

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

Alternatively, you can also use exclusively multimethods without protocols.

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

Here the first parameter will be a keyword denoting the target language, however, you could use a type as well.

;; 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]
  ...)
...

then call gen-code with a node:

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