Multiple dispatch in Hy

While tinkering with some AI routines, adderall and Hy in general, I came to conclusion that I needed something akin to multimethods in common lisp or clojure. Luckily I wasn’t the only one who had been wondering about them and quick googling found various articles detailing ways to implement them.

Adam Bard wrote a nice post with fully working code example. That I borrowed and more or less directly translated into Hy (just changed some names in the end and hacked in doc-string):

(defn multi-decorator [dispatch-fn]
  (setv inner (fn [&rest args &kwargs kwargs]
                (setv dispatch-key (apply dispatch-fn args kwargs))
                (if (in dispatch-key inner.--multi--)
                  (apply (get inner.--multi-- dispatch-key) args kwargs)
                  (apply inner.--multi-default-- args kwargs))))
  (setv inner.--multi-- {})
  (setv inner.--doc-- dispatch-fn.--doc--)
  (setv inner.--multi-default-- (fn [&rest args &kwargs kwargs] nil))

(defn method-decorator [dispatch-fn &optional [dispatch-key nil]]
  (fn [func]
    (if (is dispatch-key nil)
      (setv dispatch-fn.--multi-default-- func)
      (assoc dispatch-fn.--multi-- dispatch-key func))

That’s really not much code at all and still it does exactly what I was after (dispatching by certain key in a dictionary). One could maybe add some extra guard clauses to raise an exception if multiple methods with same dispatch-key were registered, but that wouldn’t add much more code.

But, writing functions with decorators gets bothersome eventually, so I created three small macros to automate that part for me:

(defmacro defmulti [name params &rest body]
  `(with-decorator multi-decorator
    (defn ~name ~params ~@body)))

(defmacro defmethod [name multi-key params &rest body]
  `(with-decorator (method-decorator ~name ~multi-key)
    (defn ~name ~params ~@body)))

(defmacro default-method [name params &rest body]
  `(with-decorator (method-decorator ~name)
    (defn ~name ~params ~@body)))

With these Bard’s example can be written in Hy as:

=> (defmulti area [shape]
...  (:type shape))

=> (defmethod area "square" [square]
...  (* (:width square)
...     (:height square)))

=> (defmethod area "circle" [circle]
...  (* (** (:radius circle) 2)
...     3.14))

=> (default-method area [shape]
...  (raise (Exception "Can't calculate area of this shape")))

=> (area {:type "circle" :radius 0.5})

=> (area {:type "square" :width 1 :height 1})

=> (area {:type "rhombus"})
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 6, in inner
  File "<input>", line 2, in area
Exception: Can't calculate area of this shape

Quite succinct, without being obtuse. And this system allows extensions (adding new implementations) for new cases, without the need for old ones being modified.


One thought on “Multiple dispatch in Hy

  1. Pingback: Generating scrolls – redux | Engineer's Journey

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s