Search code examples
clojurepolymorphism

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


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?


Solution

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