Is it possible to overload Clojure multi-methods on arity?

前端 未结 3 836
借酒劲吻你
借酒劲吻你 2021-02-12 13:35

I have some code that uses multi-methods and would ideally like to overload the function (in this case, multi-function) so that I can pass in a higher order function to help wit

相关标签:
3条回答
  • 2021-02-12 13:43

    You can do that using multimethods as shown below example:

    (defmulti which-colour-mm (fn [m & args] [(count args) (:colour m)]))
    (defmethod which-colour-mm [0 :blue] [m] (print m))
    (defmethod which-colour-mm [1 :blue] [m f] (f m))
    
    
    user=> (which-colour-mm {:colour :blue :object :ball})
    {:colour :blue, :object :ball}nil
    user=> (which-colour-mm {:colour :blue :object :ball} print)
    {:colour :blue, :object :ball}nil
    
    0 讨论(0)
  • 2021-02-12 13:57

    This is perfectly fine. There is the "user" interface and the "type" interface of a library. They may be identical, but they don't have to.

    The "user" interface is in your case which-colour. The "type" interface is which-colour-mm (ok, not really, but just for the sake of the argument). The user of your library does not need to know about the multimethod.

    On the other hand someone providing a new colour - say :purple - does not have to care about multi-arity boilerplate. This is handled for him in which-colour.

    This is a perfectly valid design!

    But of course there's a price tag: Suppose you have a colour, which has some more perfomant way to do things... Now, you are locked into a possible slower interface.

    To clarify this a little: Suppose you have a collection interface. You provide a function - conj - which allows the user to add elements to the collection. It is implemented like this:

    (defn conj
      [coll & elements]
      (reduce conj1 coll elements))
    

    conj1 is the "type" interface (eg. a multimethod or protocol function): it adds one element to the collection. So someone supplying a new collection type has only to implement the simple case of adding a single argument. And automagically the new type will also support adding multiple elements.

    But now suppose you have a collection type, which allows a faster way to add several elements than just adding one after the other. This capability cannot be used now.

    So you make the multimethod/protocol function the function conj itself. Now the collection can use the faster way. But each implementation must provide the multiple elements boilerplate.

    This is a trade-off and up to your decision. There is not Right Way(tm). Both can be considered idiomatic. (Although I personally would try to go with the first one as often as possible.)

    YMMV.

    Edit: An example of multi arity methods without coding in the dispatch value.

    (defmulti which-colour-mm (fn [m & args] (:colour m)))
    (defmethod which-colour-mm :blue
      ([m] (print m))
      ([m f] (f m)))
    
    0 讨论(0)
  • 2021-02-12 13:58

    Basically you can dispatch on anything, neither the type nor the number of args has to be consistent..like this:

    (defn- map-classes [an-object]
         (let [cmap 
             {1 :thing
              2  666
              3  "yada"}
        the-class (class an-object)]
        (get cmap an-object the-class)))
    
    (defn- mk-class [& args] (map #(map-classes %) args))
    (defmulti play-thing mk-class )
    (defmethod play-thing [:thing] [v] (= 1 v))
    (defmethod play-thing [666] [v] (= 2 v))
    (defmethod play-thing ["yada" String] [v x] (str x v))
    

    The possibilities are endless

    0 讨论(0)
提交回复
热议问题