Thursday, January 06, 2011

Clojure: partial and comp

Clojure provides a few different options for creating functions inline: fn (or the #() reader macro), partial, and comp. When I first got started with Clojure I found I could do everything with fn and #(); and that's a good place to start. However, as I produced more Clojure code I found there were also opportunities to use both partial and comp to create more concise code.

The following examples are contrived. They will show how partial and comp can be used; however, they aren't great examples of when they should be used. As always, context is important, and you'll need to decide when (or if) you want to use either function.

The partial function takes a function and fewer than the normal arguments to the function, and returns a function that takes a variable number of additional args. When called, the returned function calls the function with the specified args and any additional args.

The partial function can often be used as an alternative to fn or #(). The following example shows how you can use either #() or partial to multiply a list of integers by .01 (convert pennies to dollars).
user=> (map #(* 0.01 %1) [5000 100 50])  

(50.0 1.0 0.5)
user=> (map (partial * 0.01) [5000 100 50])
(50.0 1.0 0.5)
In a straightforward example, such as the one above, it's really up to you which you'd prefer. However, if you want to specify a predicate that takes a variable number of args then the case for partial starts to become a bit more noticeable.
user=> (map #(apply str "price & tip: " %&) [5000 100 50] (repeat "+") [2000 40 10])

("price & tip: 5000+2000" "price & tip: 100+40" "price & tip: 50+10")
user=> (map (partial str "price & tip: ") [5000 100 50] (repeat "+") [2000 40 10])
("price & tip: 5000+2000" "price & tip: 100+40" "price & tip: 50+10")
I haven't come across an example yet that made me think: The partial function is definitely the right choice here! However, I have passed around a few functions that had several arguments and found I prefer (partial f arg1) to #(f arg1 %1 %2 %3) and #(apply f arg1 %&).

The comp function takes a variable number of functions and returns a function that is the composition of those functions. The returned function takes a variable number of args, applies the rightmost of functions to the args, the next function (right-to-left) to the result, etc.

In a previous blog entry I used comp to return the values from a map given a list of keys. Below you can find the same example that shows the definition and usage.
user=> (def select-values (comp vals select-keys))

#'user/select-values
user=> (select-values {:a 1 :b 2} [:a])
(1)
As you can see from the example, comp creates a function that takes a map and a sequence of keys, calls select-keys with that sequence, then calls vals with the result of calling select-keys. As the documentation specifies, the functions are called from right to left.

In general I find myself executing some functions directly with some data. In that case I generally use the -> macro. For example, if I already have a map and I want a list of the keys I'm probably going to write code similar to what's found below.
user=> (-> {:a 1 :b 2} (select-keys [:a]) vals)

(1)
However, there are times when you need a function that is the composition of a few other functions. For example, taking a list of numbers, converting them to strings, and then converting them to keywords.
user=> (map (comp keyword str) [1 2])

(:1 :2)
The same thing can be done with #(), as the example below shows.
user=> (map #(keyword (str %1)) [1 2])

(:1 :2)
While the code above works perfectly well, I definitely prefer the version that uses the comp function.

Like so many other functions in Clojure, you can get by without partial and comp. However, I find my code more readable and maintainable when I use tools specifically designed to handle my current problem.

3 comments:

  1. keyword args is a great place for cleaning up my code with partial.

    i have a function that takes numerous keyword args. sometimes those keyword args are conditional.

    rather than:

    (if pred
    (my-func :this 1 :that 2 :another 3)
    (my-func :this 1 :that 2 :dif 4))

    i can do:

    (let [f (partial my-func :this 1 :that 2)]
    (if pred
    (f :another 3)
    (f :dif 4)))

    ReplyDelete
  2. Jurgen8:49 AM

    As you can see from the example, comp creates a function that takes a sequence of keys

    ... takes a map ?

    ReplyDelete
  3. Anonymous3:47 PM

    @jurgen - updated. thanks.

    ReplyDelete

Note: Only a member of this blog may post a comment.