PurelyFunctional.tv Newsletter 384: name your callbacks

Sign up for weekly Clojure tips, software design, and a Clojure coding challenge.

Issue 384 - June 29, 2020 · Archives · Subscribe

Clojure Tip 💡

name your callbacks

Let's say you're using some anonymous functions in your maps and filters. Here's some code.

(->> customers
  (filter #(>= (count (:purchases %)) 3)) ;; only customers with 3+ purchases
  (map #(apply max-key :total (:purchases %)))) ;; get biggest
purchase

This is not a ton of code, but it will make a good example. What happens if we want to name the parts so that the names reflect the intention?

There are two ways to go about it. The first way is to name the steps. The second way is to name the callbacks. I like the second way better.

1. Naming the steps

We could take the filter and map steps and name them based on their intentions.

(defn select-best-customers [customers]
  (filter #(>= (count (:purchases %)) 3) customers))

(defn get-biggest-purchases [customers]
  (map #(apply max-key :total (:purchases %)) customers))

Then we can put the names in place of the map and filter steps.

(->> customers
  select-best-customers
  get-biggest-purchases)

It makes a compact and clear threading.

2. Name the callbacks

Okay, I'm calling them callbacks because I've been writing a lot of JavaScript recently. But seeing as we don't have a better name (do we?), I'm going to call the function argument to map and filter callbacks.

Let's see what it looks like if we name the callbacks.

(defn good-customer? [customer]
  (>= (count (:purchases customer)) 3))

(defn biggest-purchase [customer]
  (max-key :total (:purchases customer)))

Then it looks like:

(->> customers
  (filter good-customer?)
  (map biggest-purchase))

Why I like method 2 better

I mentioned before that I liked naming the callbacks better. Here's why.

First, the threading is not that much harder to read. Most Clojure programmers know map and filter (and if they don't, it's time to learn them). So this should be an easy thing to read. But, further, the two functions are much more useful. They're applicable to a single customer, not collections of customers. There are many more things I can do with a function that works on one customer. You can always pass them to map and filter later if you need to.

That's not to mention that the functions that were extracted themselves are shorter and sweeter. They don't have anonymous functions like the ones from method 1 do.

Well, that's my opinion, anyway. I think the code speaks for itself. What do you think?

Podcast episode🎙

Another episode! This one is my response to Out of the Tar Pit.

Listen and watch here: My response to Out of the Tar Pit.

Clojure Challenge 🤔

Last week's challenge

The challenge in Issue 383 was to write a function that calculates the iterated square root of a number.

I love the variety of submissions. I think this is a good difficulty level to target.

Please do participate in the discussion on the gist where the submissions are hosted. It's active and it's a great way to get comments on your code.

This week's challenge

parallel lines

There are different ways to express the equation of a line. Here's one way:

2x + 4y = 1
 x + 2y = 1

The x and the y are always the same, but we can sub out variables for the three numbers:

ax + by = c

Now, we can just make a tuple with a, b, and c:

[a b c]

The first two lines represented in this form:

[2 4 1]
[1 2 1]

Okay! Now that we have a way to represent lines, your task is to write a function that takes two lines and tells us if they're parallel. Here's an example:

(parallel? [2 4 1] [1 2 1])   ;=> true
(parallel? [2 4 1] [4 2 1])   ;=> false
(parallel? [0 1 5] [0 1 5])   ;=> false ;; lines are not parallel to
themselves

I'm looking forward to seeing the variety of answers.

Thanks to this site for the challenge idea where it is considered Medium level in Python.

You can also find these same instructions here. I might update them to correct errors and clarify the descriptions. That's also where submissions will be posted. And there's a great discussion!

As usual, please reply to this email and let me know what you tried. I'll collect them up and share them in the next issue. If you don't want me to share your submission, let me know.

Rock on!
Eric Normand