Search code examples
javaoopclojurefunctional-programmingclojure-java-interop

Clojure: is it possible to inherit state of class defined with :gen-class?


I'm trying to study some Java library with Clojure as a working language. The library is (as usual in Java) very object-oriented and needs class hierarchies in client code. I've defined a class inherited from a library class with some additional methods and the data stored as a mutable dictionary in a state field:

(:gen-class
   :name my-project.my-parent-class.MyParentClass
   :extends com.example.library.LibraryClass
   :methods [[setSomeData [com.example.library.LibraryType] void]]
   :exposes-methods {libraryMethodOne parentLibraryMethodOne
                     libraryMethodTwo parentLibraryMethodTwo}
   :init init
   :state state))

(defmacro set-field!
  [this key value]
  `(dosync (alter (.state ~this) assoc ~key ~value)))

(defmacro get-field
  [this key]
  `(@(.state ~this) ~key))

(defn -init []
  [[]
   (ref {:library-object-one (LibraryObjectOne.)
         :library-object-two (LibraryObjectTwo.)})])

(defn -setSomeData [this t]
  (.setSomething (get-field this :library-object-one) t)

… ; (library methods overriding here)

I then created a child class inherited from my MyParentClass:

(:gen-class
   :name my-project.my-child-class.ChildClass
   :extends my-project.my-parent-class.MyParentClass
   :exposes-methods {libraryMethodOne myParentClassMethodOne}
   :init init
   :state state))

(defn -init []
  [[] (ref {})])
…

But I get a null pointer exception when I call (get-field this :library-object-one) macro for a ChildClass instance in a -setSomeData method — the field defined by :state is not inherited and there is no key :library-object-one in a dictionary.

Quick and dirty fix is redefine -init function in a child class like this:

(defn -init []
  [[] (ref {:library-object-one (LibraryObjectOne.)
            :library-object-two (LibraryObjectTwo.)})])

(i.e. to copy the initialization code from the parent class). But it's a terrible violation of the DRY principle. Is there a way to inherit a state from the parent class?

I understand it's not an idiomatic Clojure at all and kind of an abuse of the :gen-class API, which is provided for interoperability purposes only. Maybe I shouldn't use inheritance on my side and I must implement polymorphism in some non-OOP way (for example, by modifying functions and values stored in a state dictionary). If it is true, where can I see good examples of this approach?


Solution

  • You don't have to provide a :state for the subclasses. If you don't, it will just call the parent's method.

    (ns my-project.classes)
    
    (gen-class
      :name my_project.my_parent_class.MyParentClass
      :init init
      :state state)
    
    (defn -init []
          [[]
           (ref {:library-object-one "foo"
                 :library-object-two "bar"})])
    
    (gen-class
      :name my_project.my_child_class.ChildClass
      :extends my_project.my_parent_class.MyParentClass)
    

    And the calling namespace:

    (ns my-project.core
      (:import (my_project.my_child_class ChildClass))
      (:gen-class))
    
    (defn -main [& args]
      (let [inst (ChildClass.)]
        (println @(.state inst))))
    

    This prints:

    {:library-object-one foo, :library-object-two bar}