clojurering

Clojure: overriding one function in a library


This question is off the back of a previous question I asked here a few days ago. One of the comments was that I should dispense with the Ring middleware for extracting query parameters and write my own. One alternative that I thought I'd play with was harnessing the existing one to get what I want and I've been doing some digging into the Ring source code. It does almost exactly what I want. If I write out how I understand it works:

  1. A middleware has the function wrap-params which calls params-request
  2. params-request adds a params map to the request map, calls assoc-query-params
  3. assoc-query-params eventually calls ring.util.codec/form-decode on the incoming query string to turn it into a map
  4. form-decode uses assoc-conj to merge values into an existing map via reduce
  5. assoc-conj's docstring says

Associate a key with a value in a map. If the key already exists in the map, a vector of values is associated with the key.

This last function is the one that is problematic in my previous question (TL;DR: I want the map's values to be consistent in class of either a string or a vector). With my object orientated hat on I would have easily solved this by subclassing and overriding the method that I need the behaviour changed. However for Clojure I cannot see how to just replace the one function without having to alter everything up the stack. Is this possible and is it easy, or should I be doing this another way? If it comes to it I could copy the entire middleware library and the codec one, but it seems a bit heavyweight to me.


Solution

  • While a custom middleware is probably the clearest way to go for this problem, don't forget that you can always override any function using with-redefs. For example:

    (ns tst.demo.core
      (:use tupelo.core tupelo.test))
    
    (dotest
      (with-redefs [clojure.core/range (constantly "Bogus!")]
        (is= "Bogus!" (range 9))))
    

    While this is primarily used during unit tests, it is a wide-open escape hatch that can be used to override any function.

    To Clojure, there is no difference between a Var in your source code versus one in a library (or even clojure.core itself, as the example shows).