Wrapping deftype factory methods
(Standard disclaimer: Clojure is a new hobby.)
I'm exploring the upcoming feature in Clojure called deftype. Similar to defining a class in Java, deftype is a way of grouping related data and methods. Actually, it's quite an improvement over the old (defstruct) way. defstruct only groups related data together.
As ever, code examples are based on my side project pokerepl. This entire post focuses on the suits and values in a deck of playing cards.
Here's the old way of doing things:
(defstruct Card :value :suit)
;; instantiate it
(def card (struct Card :ace :spades))
Here's my first baby step down the new path:
#^clojure.lang.Keyword suit]
Object
(toString []
(str (get value-map value "?")
(get suit-map suit "?"))))
(def card (Card :ace :spades))
It's longer, but it does more. We get optional type hinting for the data (seen here as #^clojure.lang.Keyword). The type being extended is listed (Object in this case). The override of Object.toString means all instances will be pretty printed automatically.
What you don't see is a "constructor". There's no need because Clojure provides reasonable defaults here; the values for "value" and "suit" are passed to new instances of Card.
Unfortunately that wasn't good enough. Fortunately Clojure is a very dynamic programming language, so I could easily replace the implicitly defined factory function with my own code. To summarize, a call to (deftype Foo [bar]) generates two factory functions (Foo [bar]) and (Foo [bar meta-map ext-map]). It's possible to overwrite these but unclear how to honor the postconditions... exactly what gets returned?
So rather than replace the factory function entirely, I decided to embrace and extend it!
(defn Card
([two-chars]
(apply Card (map (partial nth (seq two-chars)) [0 1])))
([value suit]
(let [value-sym (if (= clojure.lang.Keyword (class value))
value
(get (zipmap (vals value-map) (keys value-map)) value))
suit-sym (if (= clojure.lang.Keyword (class suit))
suit
(get (zipmap (vals suit-map) (keys suit-map)) suit))]
(old-Card value-sym suit-sym))))
So that's a bit of a mess, but the requirements were too. I needed syntactical sugar to make dealing with this code palatable. And now I have said sugar. The following is now true:
(Card "AS")
(Card \A \S))
My main gripe with (deftype) is that it complicates editor support. Previously, "defn" and a relatively small list of keywords meant a list was a function definition. Functions indent differently than other types of lists. deftype means the editor is going to have to understand the source better. But, it's a small and good problem to have IMO. Also hoping for destructuring support in vector for param arguments.


