Cactus
Cactus

Reputation: 27626

case or defmulti for closed-world, value-based dispatch?

Suppose I have a closed world of valid dispatch keys; in my concrete example, it's the type of nybbles. There are two obvious ways to define an operation of some parameters that behaves differently based on a nybble argument:

  1. Using case, e.g.:

    (defn read-arg [arg-mode mem cursor]
      (case arg-mode
        0x0 [:imm 0]
        0x1 [:imm (read-fwd peek-word8 mem cursor)]
        ;; And so on
        0xf [:ram (read-fwd peek-word32 mem cursor)]))
    
  2. Using defmulti/defmethod:

    (defmulti read-arg (fn [arg-mode mem cursor] arg-mode))
    (defmethod read-arg 0x0 [_ mem cursor] [:imm 0])
    (defmethod read-arg 0x1 [_ mem cursor] [:imm (read-fwd peek-word8 mem cursor)])
    ;; And so on
    (defmethod read-arg 0xf [_ mem cursor] [:ram (read-fwd peek-word32 mem cursor)])
    

Which is considered nicer style for Clojure? Would the answer be different if the dispatch was done on symbols instead of nybbles?

Upvotes: 3

Views: 91

Answers (1)

Michał Marczyk
Michał Marczyk

Reputation: 84331

I would expect the dispatch itself to be so much faster in this case using case as to render any stylistic considerations moot – dispatching based on an integer value in a narrow range is the best case for case, really – and then I actually think that case is also better style (this really does look like closed-world dispatch, so it's good to use a mechanism that signals that).

As a further comment, it seems like the case "result expressions" will be fairly short here, which is good. If not, then it might be worth factoring them out into their own functions to prevent the method performing the dispatch from growing too large. (The JIT will then be able to inline just those portions it finds are worth inlining.)

As for whether I'd feel different if symbols where involved, that depends on a number of factors – performance (is it important? is there a big difference in benchmarks?), number and size of branches (it's good to keep overall method size small – for performance and readability reasons – so the ability to define each branch in a separate defmethod can be convenient) etc.

Upvotes: 2

Related Questions