Firstly, I'm new to Clojure, so this is likely to be stupid question.
As a learning exercise, I've got a trivial text adventure multimethod system working. I now want to change from using keywords to some form of 'classiness', which can hold data pertaining to the individual instances of 'sack', 'sword', etc.
Is defrecord
the way to go here?
The question: Can I use Clojure's derive to create a hierarchy of my defrecord class types? seem similar to this, but the accepted answer says 'no, perhaps use interfaces'.
Is the answer really no? Do I have to write all the data representations as Java classes in order to use Clojure's multimethods?
Thanks,
Chris.
Working code:
(derive ::unlit_root ::room)
(derive ::room ::thing)
(derive ::item ::thing)
(derive ::sword ::item)
(derive ::container ::thing)
(derive ::sack ::container)
(derive ::sack ::item)
(derive ::wardrobe ::furniture)
(derive ::furniture ::thing)
(derive ::wardrobe ::furniture)
(defmulti put (fn [x y z] [x y z]))
(defmethod put [::room ::thing ::thing] [x y z] "you can only put items into containers")
(defmethod put [::room ::sword ::sack] [x y z] "the sword cuts the sack")
(defmethod put [::room ::item ::container] [x y z] "ordinary success")
(defmethod put [::unlit_room ::thing ::thing] [x y z] "it's too dark, you are eaten by a grue")
(defmethod put [::room ::sack ::wardrobe] [x y z] "you win")
(defmethod put [::room ::item ::sack] [x y z] "you put it in the sack")
(defmethod put [::room ::furniture ::thing] [x y z] "it's too big to move")
Below, is what I've tried so far, but I get an error at the first derive
:
ClassCastException java.lang.Class cannot be cast to clojure.lang.Named clojure.core/namespace (core.clj:1496)
.
(defrecord Item [name])
(defrecord Weapon [name, damage])
(defrecord Furniture [name])
(defrecord Container [name])
(defrecord Bag [name])
(derive Weapon Item)
(derive Container Item)
(derive Bag Container)
(derive Furniture Container)
(def sword (Weapon. "sword" 10))
(def apple (Item. "apple"))
(def cupboard (Furniture. "cupboard"))
(def bag (Bag. "bag"))
(defmulti putin (fn [src dst] [src dst]))
(defmethod putin [Item Container] [src dst] :success_0)
The unhappy answer, as @Arthur and @noahz mentioned, is that hierarchies can't be described with classes. Where does that leave us with multimethods?
The best answer might be including a
:type
key in simple maps and dispatching on that value. You lose things like the auto-generated constructor afforded by protocols, but it's a very straightforward solution and it offers a lot of flexibility.An alternative, albeit one that suffers from over-complication, is to create a map of classes to keywords and use this to look up keywords in the hierarchy when dispatching. Again, I'll stress that you can probably find something better, but the option is there.