PurelyFunctional.tv Newsletter 382: express implicit argument

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

Issue 382 - June 15, 2020 · Archives · Subscribe

Clojure Tip 💡

express implicit argument

In Grokking Simplicity (my book about functional programming), I wanted to teach about first-class functions and higher-order functions. One of the important skills of functional programmers is to remove duplication between two functions by creating a higher-order version of them. I found three refactorings (systematic ways to transform code) for doing that. And I was surprised by how applicable these three are. Four chapters later, I'm still using them to derive all sorts of higher-order functions.

The first refactoring is called express implicit argument. That's what I'll describe this week.

When do you use it?

Look at this code:

(defn fetch-user [user-id]
  (http/get (str "https://my-api.com/v2/user/" user-id)))

(defn fetch-document [doc-id]
  (http/get (str "https://my-api.com/v2/document/" doc-id)))

This is just a simple example, but in this code we can even see that we could apply this refactoring. Notice that in the names of the functions, there is an implicit argument. The bodies of the two functions differ by only one segment of the URL string. But notice that that difference is also called out in the function names! This is an implicit argument in the function name, and that means we can apply this refactoring.

What are the steps?

There are four steps to this refactoring.

  1. Identify the implicit argument in the name of the function.
  2. Add explicit argument.
  3. Use new argument in body in place of hardcoded value.
  4. Update the calling code.

We've already done #1. fetch-user and fetch-document have implicit arguments user and document.

Let's add the explicit argument.

(defn fetch [entity-type id]

Then we use the new argument in the body in place.

(defn fetch [entity-type id]
  (http/get (str "https://my-api.com/v2/" entity-type "/" doc-id)))

Then of course we'll have to go through all of the code that calls these function and update it. The function name has changed, and it has a new argument.

What did we do? Is it better?

We made the entity-type first-class. Remember, first-class values mean you can operate on them like any other value in your code. Before, they were hardcoded. But now the entity type can be calculated, stored in a collection, or any number of things. It's now first-class. We've also gotten rid of a little bit of duplication.

Is this code better? That's hard to say without more context. It may or may not be for your particular codebase. What's important is that we have a technique we can apply if we need to.

Book update 📖

You can buy Grokking Simplicity and use the coupon code TSSIMPLICITY for 50% off.

I've been making steady progress on Chapters 10-13. These chapters are all about first-class functions; higher-order functions; map(), filter(), and reduce(); and functions for modifying nested data. These are super important ideas in functional programming, and I'm glad I found a good way to teach them.

I always like when I reach the point in a chapter where I can finally show the depth of the ideas. The surface of the idea is important, and it has to be useful, but the deeper ideas are where it gets really interesting. It's hard to switch gears in my head, but once I do, it's really satisfying to be able to share a small experience of the deep ideas in functional programming.

Clojure Challenge 🤔

Last week's challenge

There was no challenge in Issue 381.

This week's challenge

unique values

Let's keep it easy this week as we get back into it.

Write a function called uniques that takes a sequence of values. That sequence is going to have zero or more unique values. The rest will repeat at least once. Your job is to return the unique values in the same order as they appear in the argument sequence.


(uniques [1 2 3 4 5 6 1 2 3 5 6]) ;=> (4)
(uniques [:a :b :c :c]) ;=> (:a :b)
(uniques [1 2 3 1 2 3]) ;=> ()

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