Protocols more functional than if in Clojure?

While reading Ring Handlers – Functional Decorator Pattern

How often do you perform conditional branching with if? The answer may likely rely upon the available features offered by the language of choice – the if statement/expression (Java, Clojure), pattern matching (F#) and dynamic dispatch (Java, Clojure).

When I read Conditional (programming) in Wikipedia I stumbled upon the following sentence:

“Although dynamic dispatch is not usually classified as a conditional construct, it is another way to select between alternatives at runtime.”

It made me think about its implementation in Clojure. I could definitely tackle it with method overloading in Java (that was dumb easy to figure out), but what about Clojure?

At the moment I’d come up with a possible implementation in Java, Clojure’s Protocols sprung to my mind. I’ve been reading about them, mainly in books and very rarely in articles, and have never used the concept before. It looked like it’d change.

Protocols are like interfaces in Java and beg no special treatment of nil (counterpart of Java’s null which begs a casting to appropriate type).

Below you can find a simple Protocol implementation in Clojure and its use. Fire up lein2 repl and follow along.

;; I'm aiming at simplifying the following function from the article Ring Handlers – Functional Decorator Pattern
;; It's actually Ring's function
user=> #_
(defn wrap-params
  [handler & [opts]]
  (fn [request]
    (let [request  (if (:query-params request)
                           request
                           (assoc-query-params request))]
      (handler request))))

;; I'm mostly interested in the if expression in let
user=> #_
(if (:query-params request)
    request
    (assoc-query-params request))

;; the very first version to demonstrate protocols
user=> (defprotocol IfProtocol
         "A better if - an if or cond alternative - ver.1"
         (if++ [this] "A single protocol method"))
IfProtocol
user=> (extend-protocol IfProtocol
         nil
         (if++ [_] "Nil")
         Object
         (if++ [_] "Object"))
nil
user=> (if++ nil)
"Nil"
user=> (if++ "a string")
"Object"

;; a two-argument version
user=> (defprotocol IfProtocol
         "A better if - an if or cond alternative"
         (if++ [this rq] "A single protocol method with a request"))
IfProtocol
user=> (extend-protocol IfProtocol
         nil
         (if++ [_ rq] (str "Nil with " rq))
         Object
         (if++ [_ rq] (str "Object with " rq)))
nil
user=> (if++ nil 1)
"Nil with 1"
user=> (if++ "A string" 1)
"Object with 1"

;; the final version - extending the protocol one type at a time
user=> (extend-protocol IfProtocol Object (if++ [_ rq] rq))
nil
user=> (defn assoc-query-params [rq]
         (str "Processing " rq))
#'user/assoc-query-params
user=> (extend-protocol IfProtocol nil (if++ [_ request] (assoc-query-params request)))
nil

;; Testing...

user=> (defn f [request]
         (let [request  (if++ (:query-params request) request)]
           request))
#'user/f
user=> (f {})
"Processing {}"
user=> (f {:query-params 1})
{:query-params 1}

;; the function from the article with if++
user=> (defn wrap-params
         [handler & [opts]]
         (fn [request]
           (let [request  (if++ (:query-params request) request)]
             (handler request))))
#'user/wrap-params

;; more testing
user=> (def wp (wrap-params identity))
#'user/wp
user=> (wp {})
"Processing {}"
user=> (wp {:query-params 1})
{:query-params 1}

It works, but I’m still uncertain about its applicability for a simple if-then expression, meaning as an alternative for a flat, single if-then expression as in the use case above. It appears to bring no value in simple cases and may result in more work for not much benefit. Does it?

You may also find OCaml: Pattern matching vs If/else statements or if-else branching in clojure useful to tackle the issue. The last reference made me thinking about nested if statements with their own else branches. Doh, I thought I was done with it.

There’s also Clojure’s multimethods concept which seem as much applicable to the use case as protocols. Doh doubled.

I’ve also added org.clojure/core.match to a S.I.L (= Seemingly Inevitable to Learn) bucket.

Be Sociable, Share!
This entry was posted in Languages.

7 Responses to Protocols more functional than if in Clojure?

  1. Paul says:

    In this case, core.match or even simple dispatch tables would achieve exactly what you’re looking for.

    Typically in a Lisp, `if` is used for shaping a return type.

    Paul

  2. Konrad Garus says:

    I’d say the contrary. Protocols feel more like polymorphism and are strongly tied to types. This is not flow control.

    This if++ above obscures the meaning completely. I think that’s wrong.

    The problem in Ring would be much easier solved with a simple higher order function:

    let [request (lazy-assoc request :query-params parse-query-params)]

    … but it still is not as clear as the old good “if”. :-)

    • You’re right. It’s not a flow control mechanism per se, but since the flow control in the example relied upon nil or not-nil values we could (and I did) use it to differentiate between them, couldn’t we?

      • Paul says:

        Usually in the nil/not-nil case you should prefer “when” or the maybe-monad to handle flow.
        `if` should only be used if indeed you want to shape the return of both cases.

        Protocols are only used to unifying an abstraction across pieces of code. It works in this case, but there is no real benefit for doing so.

        Again, the Higher-Order pattern to use here is typically a dispatch table (either through a hash map or some form of pattern matching)

        • Hi Paul,

          It’s only now when I realized what you meant by “a dispatch table”. While reading Reversi in Clojure w/ Three Alternative User Interfaces I ran across the following snippet:

          (defn opposite-piece [piece]
            "Returns the opposing-piece of the specified piece"
            (if (= :w piece) :b :w ))

          It struck me, but wasn’t able to find out why. I spent a very short moment on the solution with a map:

          (defn opposite-piece-with-map [piece]
            (let [m {:w :b :b :w}]
              (piece m)))

          The map is inside the function, but could be externalized if needed. Do you think it’s more functional? Is that what you had in mind with the dispatch table concept?

          • Paul says:
            (def dispatch-table {:ident-one (fn [x] x)
                                           :ident-two (fn [x] (* x 2))})

            ;; some-map might look like {:identity :ident-one, :payload 5 ...

            (defn handler [some-map]
              (let [{:keys [identity payload config]} some-map
                     handler-fn (dispatch-table identity)]
                (handler-fn payload)))

            A dispatch table, conceptually is like if you merged a `case` statement and closed-form multi-method.

            Much like how the example you cited is using keywords to signal, the dispatch table will dispatch based on its keys (which can be anything: keywords, types, arg lists as a vector).

            It can then do one of two things: return the function to call, or call the appropriate function and return its result. I prefer the former, but it depends on the context.

  3. Pingback: Reversi in Clojure…functional | Japila :: verba docent, exempla trahunt

Leave a Reply

%d bloggers like this: