PurelyFunctional.tv Newsletter 383: replace body with callback
Issue 383 - June 22, 2020 · Archives · Subscribe
Clojure Tip 💡
replace body with callback
Last week I talked about a refactoring from my book Grokking Simplicity. I mentioned that I found three refactorings that were super applicable. Well, I miscounted. There are only two! This week, we'll talk about the second one. It's called replace body with callback.
When do you use it?
This refactoring is how you make a function that takes another function as an argument. Here is some code.
(defn parse-int [string]
(try
(Integer/parseInt string)
(catch Exception e
nil)))
(defn get-url [url]
(try
(slurp url)
(catch Exception e
nil)))
Both of these functions are catching and ignoring exceptions. There must be a way to get rid of the duplication. Let's try to chop it up in a traditional way.
Typically, if we have code we'd like to deduplicate, we just pull it out into its own function. Here's an example.
(defn run-test-one []
(setup1)
(setup2)
(setup3)
(test-one)
(tear-down1)
(tear-down2))
(defn run-test-two []
(setup1)
(setup2)
(setup3)
(test-two)
(tear-down1)
(tear-down2))
Both of these functions are doing something very similar: there are some setup steps, then the test is run, then there are some teardown steps. We could easily pull out the setup and teardown steps into their own functions.
(defn setup []
(setup1)
(setup2)
(setup3))
(defn tear-down []
(tear-down1)
(tear-down2))
Then you can use them like this:
(defn run-test-one []
(setup)
(test-one)
(tear-down))
(defn run-test-two []
(setup)
(test-two)
(tear-down))
We've gotten rid of quite a bit of the duplication. But we can't do this
with out first two functions. There's no way to separate out the try
from the catch
. They are syntactically linked and can't be written in
two separate functions. So how to get rid of this duplication?
The answer is this refactoring. It can even make our two test functions even less duplicative.
What are the steps?
There are three steps to replace body with callback.
- Identify the before, body, and after sections.
- Extract the whole thing into a function.
- Extract the body section into a function passed as an argument to that function.
Let's go through these and apply the refactoring.
The before, body, and after sections are the three sections of your function. It's good to have two similar functions to compare. The before and after sections won't change between functions, while the body will. I've labeled the sections below.
(try ;; before
(Integer/parseInt string) ;; body
(catch Exception e nil)) ;; after
The key is that the body is going to be passed in as a function argument. That way you can pass in different behavior. Now that we've identified the before, body, and after sections, it's time to extract it into a function.
(defn ignore-errors []
(try ;; before
(Integer/parseInt string) ;; body
(catch Exception e nil)) ;; after
)
The final step is to extract the body into a function argument.
(defn ignore-errors [f]
(try ;; before
(f) ;; body
(catch Exception e nil)) ;; after
' )
Now we can use this function like so:
(defn parse-int [string]
(ignore-errors #(Integer/parseInt string)))
(defn get-url [url]
(ignore-errors #(slurp url)))
It's crazy to me how these two refactorings keep paying dividends for
creating higher-order functions. In the book, we use them to derive
map
, filter
, and reduce
and many other functions.
Podcast episode🎙
Things are opening back up over here and I found a few quiet hours to record a new episode. In this one, I read excerpts from the classic paper Out of the Tar Pit. Check out the podcast here.
Clojure Challenge 🤔
Last week's challenge
The challenge in Issue 382 was to write a function that kept only unique values in a sequence.
This challenge got tons of submissions. You can see them here. I love that it got so many and I think it was because it was a bit easier. I'll try to target the same difficulty level again.
Please do participate in the discussion on the gist where the submissions are hosted. It's already active and it's a great way to get comments on your code.
This week's challenge
iterated square root
If you iteratively apply the square root, you will eventually get a number strictly less than two. Write a function that figures out how many times you have to apply the square root it results in a number less than two.
Examples
(iter-sqrt 1) ;=> 0 (since 1 is already less than 2)
(iter-sqrt 2) ;=> 1 (since the square root of 2 is less than 2)
(iter-sqrt 256) ;=> 4
Bonus: Write it in a way that uses stack frames and in a way that doesn't.
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