comp and partial vs ->>

1,632 views
Skip to first unread message

JHacks

unread,
Oct 27, 2016, 10:56:42 AM10/27/16
to Clojure
I have some confusion about how the function `comp` works, especially as
compared to the threading macro `->>`.

From the book *Clojure Programming* (pages 70-71 of Chapter 2: Functional
Programming), the following two functions are described as functionally
equivalent:

    (def camel->keyword
      (comp keyword
            str/join
            (partial interpose \-)
            (partial map str/lower-case)
            #(str/split % #"(?<=[a-z])(?=[A-Z])")))

    (defn camel->keyword*
      [s]
      (->> (str/split s #"(?<=[a-z])(?=[A-Z])")
           (map str/lower-case)
           (interpose \-)
           str/join
           keyword))

Why does the first function, `camel->keyword`, need to use `partial` with the
`map` and `interpose` functions? The second function, `camel->keyword*`, does
not need to use `partial`.

lvh

unread,
Oct 27, 2016, 10:58:04 AM10/27/16
to clo...@googlegroups.com
Hi,

comp takes a number of functions and returns a function. Threading macros take a number of forms (expressions) and return an expression. The threading macro does not need a partial, because it operates on the form and injects something in it (in the second position for ->, last position for ->>). The comp version does need a partial, because `(interpose \-)` is not a function — it is just a form.

lvh


--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Gary Trakhman

unread,
Oct 27, 2016, 11:01:06 AM10/27/16
to Clojure
Comp does its work at run-time, so you have to call functions that return functions.  Threading macros do their work at compile-time, so your form literally compiles to this:

 
> (clojure.walk/macroexpand-all '(->> (str/split s #"(?<=[a-z])(?=[A-Z])")
           (map str/lower-case)
           (interpose \-)
           str/join
           keyword))
(keyword
  (str/join
    (interpose \-
      (map str/lower-case
        (str/split s #"(?<=[a-z])(?=[A-Z])")))))


Alan Thompson

unread,
Oct 27, 2016, 12:39:27 PM10/27/16
to clo...@googlegroups.com
I almost never use either the `comp` or the `partial` functions.  I think it is clearer to either compose the functions like Gary showed, or to use a threading macro (my favorite is the `it->` macro from the Tupelo library).
Alan


For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

Anon Mouse

unread,
Oct 27, 2016, 1:28:02 PM10/27/16
to Clojure
as-> is an alternative to using Tupelo's it->
as-> allows you to use a contextually meaningful identifier vs using 'it.

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

JHacks

unread,
Oct 27, 2016, 3:34:19 PM10/27/16
to Clojure
Thanks, everyone, your responses are very helpful and much appreciated.

On Thu 10/27/16 09:57AM, lvh wrote:

> comp takes a number of functions and returns a function. Threading macros take a
> number of forms (expressions) and return an expression. The threading macro does
> not need a partial, because it operates on the form and injects something in it
> (in the second position for ->, last position for ->>). The comp version does
> need a partial, because `(interpose \-)` is not a function — it is just a form.

On Thu 10/27/16 03:00PM, Gary Trakhman wrote:

> Comp does its work at run-time, so you have to call functions that return
> functions.  Threading macros do their work at compile-time, so your form
> literally compiles to this:

These points are helpful for me to keep in mind:

* `comp` works with functions, and `->>` works with forms.

* `comp` is runtime, while `->>` is compile-time. The arguments to `comp` will
   be evaluated at runtime and should be callable functions.

Looking at the macro expansion of `->>` is very helpful and straightforward to
understand. The `->>` macro will setup an expression at compile-time that will
be evaluated all at once during runtime.

For `comp`, at runtime, `(map str/lower-case)` and `(interpose \-)` will return
transducers, and `partial` is needed to create the intended function, e.g.,
`str/lower-case` bound to `map` and expecting a collection argument.


Marek Kubica

unread,
Oct 27, 2016, 6:24:19 PM10/27/16
to JHacks, clo...@googlegroups.com
Hi,

On Thu, 27 Oct 2016 12:34:19 -0700 (PDT)
JHacks <jhack...@gmail.com> wrote:

> For `comp`, at runtime, `(map str/lower-case)` and `(interpose \-)`
> will return
> transducers, and `partial` is needed to create the intended function,
> e.g., `str/lower-case` bound to `map` and expecting a collection
> argument.

You're almost correct. This has nothing to do with transducers and was
possible before there were transducers in Clojure (though composition
of transducers works in a similar way)

What `partial` does is to create anonymous functions out of functions
and arguments. It inserts these arguments starting from left to right
into the function. This process is sometimes called currying.

So with your example you want a function which takes a string and
converts every letter to a word. So you could write an anonymous
function like

(fn [coll]
(map str/lower-case coll))

But that is rather verbose and naming the `coll` argument is kinda
pointless, so you can simplify it to

#(map str/lower-case %)

And as you see, have a function which calls a function (`map`) with
the first n arguments (in this case 1) pre-set (`str/lower-case`).
Therefore you can use `partial` do do just that:

(partial map str/lower-case)

All of these functions are equivalent and you can use them in the
`comp` call. There are some stylistic disagreements whether `#()` or
`partial` is nicer, but that's a story for another time :)

regards,
Marek

Mark Engelberg

unread,
Oct 27, 2016, 8:14:05 PM10/27/16
to clojure
On Thu, Oct 27, 2016 at 9:39 AM, Alan Thompson <cloo...@gmail.com> wrote:
I almost never use either the `comp` or the `partial` functions.  I think it is clearer to either compose the functions like Gary showed, or to use a threading macro (my favorite is the `it->` macro from the Tupelo library).
Alan


You need to use comp if you're building a transducer.

Timothy Baldridge

unread,
Oct 27, 2016, 8:44:14 PM10/27/16
to clo...@googlegroups.com
I use comp all the time, not only for transducers, but also for digging into maps:


(map (comp first :pets)
   [{:pets [:fluffy]}
    {:pets [:spot]}])

=> (:fluffy, :spot)

Partial is also handy when used with a lot of sequence functions

(->> [1 2 3 4 5]
       (filter (partial > 3)))

Sure I could write that function as #(< 3 %), but I find that syntax harder to mentally parse as I have to remember that the body of the #() is a function context and then I have to look up where the % symbol is. (partial > 3) is just easier to understand. 

Timothy 



--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+unsubscribe@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
“One of the main causes of the fall of the Roman Empire was that–lacking zero–they had no way to indicate successful termination of their C programs.”
(Robert Firth)

JHacks

unread,
Oct 27, 2016, 10:57:20 PM10/27/16
to Clojure, jhack...@gmail.com


On Thursday, October 27, 2016 at 6:24:19 PM UTC-4, Marek Kubica wrote:

But that is rather verbose and naming the `coll` argument is kinda
pointless, so you can simplify it to

#(map str/lower-case %)

And as you see, have a function which calls a function (`map`) with
the first n arguments (in this case 1) pre-set (`str/lower-case`).
Therefore you can use `partial` do do just that:

(partial map str/lower-case)

All of these functions are equivalent and you can use them in the
`comp` call. There are some stylistic disagreements whether `#()` or
`partial` is nicer, but that's a story for another time :)

Thanks, it's helpful to consider the anonymous function literal version of the `map` with `str/lower-case` function. I think that also highlights why the first function in the `comp` doesn't use `partial` and instead uses `#()', because of the position of the variable argument:

Bobby Eickhoff

unread,
Oct 28, 2016, 7:35:29 AM10/28/16
to Clojure
I agree that forms like (partial > 3) are clearer than #() forms.  However, I've been avoiding partial in code bases for a while -- it was measurably slower than the alternative.  Is that still the case?  Has anyone else observed slowness with partial?


On Thursday, October 27, 2016 at 8:44:14 PM UTC-4, tbc++ wrote:
I use comp all the time, not only for transducers, but also for digging into maps:


(map (comp first :pets)
   [{:pets [:fluffy]}
    {:pets [:spot]}])

=> (:fluffy, :spot)

Partial is also handy when used with a lot of sequence functions

(->> [1 2 3 4 5]
       (filter (partial > 3)))

Sure I could write that function as #(< 3 %), but I find that syntax harder to mentally parse as I have to remember that the body of the #() is a function context and then I have to look up where the % symbol is. (partial > 3) is just easier to understand. 

Timothy 


On Thu, Oct 27, 2016 at 6:12 PM, Mark Engelberg <mark.en...@gmail.com> wrote:
On Thu, Oct 27, 2016 at 9:39 AM, Alan Thompson <cloo...@gmail.com> wrote:
I almost never use either the `comp` or the `partial` functions.  I think it is clearer to either compose the functions like Gary showed, or to use a threading macro (my favorite is the `it->` macro from the Tupelo library).
Alan


You need to use comp if you're building a transducer.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Timothy Baldridge

unread,
Oct 28, 2016, 8:54:32 AM10/28/16
to clo...@googlegroups.com
That was fixed in a patch that added special cases to partial when used with smaller numbers of arguments. It was never much slower, but it should be just as fast as hand made functions now. 



For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Mars0i

unread,
Oct 29, 2016, 12:44:24 AM10/29/16
to Clojure
On Thursday, October 27, 2016 at 11:39:27 AM UTC-5, Alan Thompson wrote:
I almost never use either the `comp` or the `partial` functions.  I think it is clearer to either compose the functions like Gary showed, or to use a threading macro (my favorite is the `it->` macro from the Tupelo library).
Alan

I may be weird, but I almost never use the threading macros, and routinely use comp and partial, often with map. I often order function parameters in my definitions to facilitate the use of partial.  I don't mind reading functions "backwards" in a long chain of function calls, even though I sometimes use threading macros. I think this is just a matter of taste and past experience.

I agree that it can be clearer sometimes to arrange function calls "forward" in the order in which they're called.  Obviously, a (small) disadvantage of e.g. -> and ->> macros is that they depend on having a series of function calls in which it's the same argument (1st, or last) that has to be passed on (although you can wrap the calls in e.g. #() to fudge that).  I think that for a new user it's probably a little bit confusing with -> to have a series of function calls in which there's a hidden argument right after the function name.  With normal function composition, all arguments are explicit, and you can pass the result of any function call as any argument.  I don't think anyone would disagree with these points.  Taste and past experience.

Mikera

unread,
Oct 30, 2016, 10:50:08 PM10/30/16
to Clojure
I actually prefer the following style to both of the above:

(defn camel->keyword*
         [s]
         (let [words (str/split s #"(?<=[a-z])(?=[A-Z])")
               lc-words (map str/lower-case words)
               joined-words (str/join "-" lc-words)]
           (keyword joined-words))) 

Reasons:
- Your intermediate values are explicitly named, which helps to make the code self-describing
- It is (marginally) more performant than the composed function case (I think exactly matches the threading macro)
- You can use the intermediate values in more than one of the following steps if needed, which can make refactoring / adding new features easier
- The ordering is (to me) more logical as it describes the stages of the transformation in the order they are performed.
- It is less "Clever". Clever code is generally bad for maintenance and future understanding. Both functional composition and the code-transformation effects of the threading macro represent conceptual overhead that you don't need to pay (in this case).


 

Alan Thompson

unread,
Oct 30, 2016, 11:44:01 PM10/30/16
to clo...@googlegroups.com
I agree 100%.  I quite frequently use the style, more than any of the alternatives.  It even has a name, "Introduce Explaining Variable":

Alan​


--

JHacks

unread,
Oct 31, 2016, 9:48:35 AM10/31/16
to Clojure


On Sunday, October 30, 2016 at 10:50:08 PM UTC-4, Mikera wrote:

I actually prefer the following style to both of the above:

(defn camel->keyword*
         [s]
         (let [words (str/split s #"(?<=[a-z])(?=[A-Z])")
               lc-words (map str/lower-case words)
               joined-words (str/join "-" lc-words)]
           (keyword joined-words))) 

Reasons:
- Your intermediate values are explicitly named, which helps to make the code self-describing
- It is (marginally) more performant than the composed function case (I think exactly matches the threading macro)
- You can use the intermediate values in more than one of the following steps if needed, which can make refactoring / adding new features easier
- The ordering is (to me) more logical as it describes the stages of the transformation in the order they are performed.
- It is less "Clever". Clever code is generally bad for maintenance and future understanding. Both functional composition and the code-transformation effects of the threading macro represent conceptual overhead that you don't need to pay (in this case).

 Wow! I much prefer this style. I'm impressed that even though it's clearer (to me), it doesn't suffer in performance.

Thanks for not only showing a clearer way to do this, but also outlining good arguments for why one might want to.

Leon Grapenthin

unread,
Nov 3, 2016, 2:48:59 PM11/3/16
to Clojure
This style is only good if you do damn good naming on your "explaining variables". So while more names can really improve readability, bad names do the opposite by adding confusion and misdirection. This pattern enforces naming for every step. Good names are difficult, so I can only recommend to not use this style as a "silver bullet" or default. If you don't have time for the names in the first place, prefer threading macros, which still provide for great readability and can be edited with less typing overhead.

Note that you could have used the same names as right hand comments in the threading macro which IMO is the best of both worlds.

Regarding this discussion in general, I find #(my-fn %) to be preferable over (partial my-fn) in most cases. My understanding is that partial was introduced to the language before the terse lambda #/% syntax and is mostly superseded by it. 

In summary comp and partial are specifically useful if you transform functions algorithmically! If you just need to chain a few things while typing, #(... %) syntax and the arrow macro are most intuitive to write and read.

Sean Corfield

unread,
Nov 3, 2016, 3:11:19 PM11/3/16
to Clojure Mailing List

The other thing to watch out for when naming intermediate results is that you don’t hold onto the head of large sequences. It’s not a problem with these book examples but can be a problem in large scale programming.

 

Overall tho’, the readability of these different forms is very subjective. I personally do not like the #(my-fn %) form – I find the added “punctuation” to be noisy in comparison to (partial my-fn) – and we have a ‘flip’ function in our “utility” library to handle the first-argument-omitted case where partial is not suitable so we don’t need #(my-fn % :b :c) either. I prefer -> / ->> over composition of (curried) functions from a readability p.o.v. but I know other folks who prefer ‘comp’.

 

Sean Corfield -- (970) FOR-SEAN -- (904) 302-SEAN
An Architect's View -- http://corfield.org/

"If you're not annoying somebody, you're not really alive."
-- Margaret Atwood

--

You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to


For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.

To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

Reply all
Reply to author
Forward
0 new messages