Learning Clojure

Hello everyone,
I’m learning Clojure now, I’m coming from imperative programming, so I have problems understanding execution flow of that code:

(defn chop-chop [coll]
  (let [x (partition-by identity coll)]
    (map list (map (comp str first) x)
                (map count x))))
=> (chop-chop "aaaabbbcca")
=> (("a" 4) ("b" 3) ("c" 2) ("a" 1))

I understand that we

  1. create here function chop-chop with parameter coll
  2. then we apply function partition-by for coll, but I’m not sure what identity is
  3. next we bound result of previous operation to x
  4. But I can’t understand the next lines of code:
(map list (map (comp str first) x)
(map count x))))

Could someone explain to me step-by-step execution flow of that program?
Thanks a lot!

Welcome!
It’s great that you’re learning Clojure, and while it may seem just weird now, stick with it, and soon it’ll become second nature…

First things first, if you are at the REPL, you have several ways of figuring what functions do, the first one being to run them… So in the case of identity, you could try doing (identity 1) or (identity "foo"), maybe. The name is a bit of a giveaway, too.

Also, being at the REPL you can do (doc identity), which in this case prints out:

-------------------------
clojure.core/identity
([x])
  Returns its argument.

The second thing you should do, is to properly format the code, which makes it much easier to read:

(defn chop-chop [coll]
  (let [x (partition-by identity coll)]
    (map list
         (map (comp str first) x)
         (map count x))))

You were quite spot on with your analysis so far. partition-by splits a collection using a function, and separates the groups when the value returned by that function changes. When you pass identity to it, it will group by elements that are equal.

So your x will look like this:

(partition-by identity  "aaaabbbcca")
;; => ((\a \a \a \a) (\b \b \b) (\c \c) (\a))

Those are sequences of characters, which is not quite what you’re after, but it gets you almost there.

The next line is a it confusing, but we can separate it into parts to better understand it. comp composes functions, so basically it gives you a new function that first applies first to its argument, and then str to the result of first. You are mapping that function over your x, so that will give you a list of single letter strings, one for each group in the result of partition-by. We can see what this looks like:

(->>   "aaaabbbcca"
       (partition-by identity)
       (map (comp str first)))
;; => ("a" "b" "c" "a")

map can take several collections, so we should look at what the second collection looks like:

(->>   "aaaabbbcca"
       (partition-by identity)
       (map count))
;; => (4 3 2 1)

So now, you are mapping list over two collections, one containing the single letter strings, and one containing the count of each group in the partitioned list:

(chop-chop “aaaabbbcca”)
;; => ((“a” 4) (“b” 3) (“c” 2) (“a” 1))

And there you have the result you were after. (IIRC, the original problem statement for this asked for a vector, so you can wrap that result in vec to get:

(vec (chop-chop  "aaaabbbcca"))
;; => [("a" 4) ("b" 3) ("c" 2) ("a" 1)]

Hope that clarifies a bit what’s going on :slight_smile:

Remember to use the REPL to experiment and get information on those functions you don’t know yet. Also, you can use https://clojuredocs.org to get the documentation enriched with a bunch of examples. It is a great resource.

6 Likes

Thank you very much Martin!
Great explanation! I’ll try my best to learn the language!

1 Like

You’re welcome!

1 Like