PurelyFunctional.tv Newsletter 368: Refactoring: extract calculation from action

Issue 368 - March 09, 2020 · Archives · Subscribe

Clojure Tip 💡

Refactoring: extract calculation from action

One of the most basic refactorings in functional programming is to extract out a calculation from an action. These two are the terms I'm using in my book. Calculations are pure functions, whereas actions are impure functions. This refactoring moves code out of an action and into a new calculation.

Why? Well, calculations are easier to test and reuse. The more code we have in calculations, the easier our entire codebase will be to test and reuse.

Let's take a look at the refactoring in action. Let's say we have a function that sends an email to our high spending customers. Here's some code:

(defn send-high-spender-campaign [customers]
  (doseq [customer customers]
    (when (> (:total-purchases customer) 1000)
      (send-high-spender email customer))))

First: this works. It's fine. But we notice that there's a bit of logic serving as the test for our when expression. That's a useful bit of code that we want to reuse. The marketing team themselves wants to use it to generate high-spender reports. We can extract it out into its own function.

(defn high-spender? [customer]
  (> (:total-purchases customer) 1000))

(defn send-high-spender-campaign [customers]
  (doseq [customer customers]
    (when (high-spender? customer)
      (send-high-spender email customer))))

high-spender? can be tested easily and of course reused.

This is a simple refactoring and a very common one. In fact, it's a version of extract function with the special condition that what you extract is a pure function (calculation) from an impure function (action).

Is it useful to call out and name? I think so. This kind of refactoring is the first cleanup I do when I find a mess I don't know how to approach. Giving names to the smaller pieces and pulling them out into their own functions is something you can do even if you don't understand the big pict ure. And focusing on calculations can help build your understanding of the domain.

Book update 📖

Ho ho ho! Look who's two chapters ahead of the early access edition of Grokking Simplicity, my book on functional programming! What I mean is, I've written and submitted chapters 8 & 9. Chapters 8 & 9 are about stratified design. It was quite a challenging topic, and of course I couldn't say everything I wanted to about the topic, but those two chapters are packed full of great design advice that I'll be able to add to throughout the rest of the book. These two chapters complete the first part, all about Actions, Calculations, and Data.

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

Chapters 8 & 9 won't be published just yet. They'll come out regularly, about one per month. That gives me some much-needed breathing room to work on the next part of the book. Part 2 is all about higher-order functions. We start with higher-order calculations (how to refactor to eliminate syntactic boilerplate), take a turn into the common "data transformation pipeline" pattern, then go into first-class mutable state, queues, and coordination. Functional programmers have a lot to say about making actions (side-effects) safer and it's an important part of the paradigm.

Thanks everyone for being there (and for buying the book, if you have). I've had tons of great feedback, from encouragement, to spotting typos, to critique of my code style. It all helps! You rock!

Clojure Challenge 🤔

Last week's challenge

The challenge in Issue 367 was to implement the Caesar cipher. There were many submissions. You can see them here.

I was really happy with the variety of implementations. Two approaches diverged: some people did ASCII code math, some people used data structures. But there was still a lot of variety! Sometimes the submissions are kind of similar. Not this time.

You can leave comments on these submissions in the gist itself. Please leave comments! You can also hit the Subscribe button to keep abreast of the comments. We're all here to learn.

This week's challenge

Remove last vowel from words in a sentence

Write a function that removes the last vowel from every word in a sentence. You can split words by spaces. Vowels are a, e, i, o, and u.

Examples:

(remove-last-vowels "Hi, there!") ;=> "H, ther!"
(remove-last-vowels "This is not a test.") ;=> "Ths s nt
tst."
(remove-last-vowels "Hippopotamus") ;=> "Hippopotams"

Thanks to this site for the idea. This is kind of a silly one, but I thought it would be good for testing our Clojure skills.

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