Multimethods in multiple namespaces
One of the multimethods that I use on the Tic Tac Toe project is related to how players select a spot on the board. The hardest thing to do while implementing the methods was, actually, dealing with dependencies. I had to create a separated namespace, ttt.get-spots
that would hold only select-spot
and its methods.
ttt.get-spots
ended up depending on many other namespaces, mostly those related to each player. It needed ttt.negamax
to handle the unbeatable computer, ttt.prompt
to deal with the user input, among others. The ttt.get-spots
was then required in the namespace that handle the game loop.
Concrete vs abstract
My first thought was that, if I had the concretion (defmethod
) separated from the abstraction (defmulti
), I would end up using conditionals to call the methods: if player is unbeatable computer, use ai/select-spot
, if player is user…
I was wrong. It is possible to separate them and call only select-spot
, without specifying the namespace that each method comes from, but only the one where defmulti
is. Anything that uses select-spot
needs to know only about the abstraction.
Here is how the separation worked out:
get_spots.clj
(ns ttt.get-spots)
(defmulti select-spot (fn [player & params] (:role player)))
prompt.clj
(ns ttt.prompt
(:require [ttt.get-spots :refer [select-spot]]))
(defmethod select-spot :human
[player params])
negamax.clj
(ns ttt.negamax
(:require [ttt.get-spots :refer [select-spot]]))
(defmethod select-spot :hard-computer
[player params])
easy_computer.clj
(ns ttt.prompt
(:require [ttt.get-spots :refer [select-spot]]))
(defmethod select-spot :human
[player params])
And the namespace that uses select-spot
:
(ns ttt.game-loop
(:require [ttt.get-spots :as spots]))
(defn game-loop
[player params]
; ...
(spots/select-spot player paramas))
When to separate them
I use multimethods in other situations, but I didn’t separate them. One case is, for instance, the final message of the game, that also depends on what role the winner. The idea is that, when we want to protect those who use that namespace from the details of the implementation, we separate them. Otherwise, when the multimethod is more like an alternative syntax for cond
, they can stay at the same place.