Jake McCrary

Reading in 2018

At the beginning of every year I like to take the time to reflect on my previous year’s reading. It gives me a time to correct my data and think about where I want my reading to go in the upcoming year.

Here are links to my previous end-of-year reflections: 2013, 2014, 2015, 2016, and 2017.

I’ve continued to keep track of my reading using Goodreads. My profile continues to have the full list of the books I’ve read since 2010. Here is my entire 2018 record.

I slacked off a bit when writing reviews for all of my read books in Goodreads. I often didn’t write a review until some time had passed after completing the book and, as a result, I think I did a worse job reviewing books. Some books don’t even have a written review. I’m not a fan of this and will push myself some in 2019 to do a better job.

2018 Goal

There are a few more books on writing that I’ve wanted to read for a while. I’m planning on reading at least one of them this year. I’m also want to read more Octavia Butler. - Me (in the previous reading post)

That was my goal for 2018. It breaks down into two goals:

  1. Read at least one book on writing.
  2. Read more Octavia Butler.

I succeeded on the Octavia Butler goal and completely failed with the other.

2018 Numbers

I read 43 books for a total of 16,213 pages. This is a bit less than last year but still a fair amount.

Highlights

Below is a list of my five star books from 2018. The book titles link to Amazon and are affiliate links. The other links are to my Goodreads review. Unfortunately, this year I didn’t do a great job of always writing a review so some of them are missing or very short.

I generally highlight a lot of passages while reading and then rarely go back to look at them. I’ve included links to my highlights. Are they worthwhile without the context of the book? I have no idea. I’ve reread them and got something out of them but many are also so far removed from my memory that they are almost useless.

Being Mortal: Medicine and What Matters in the End by Atul Gawande

This book deals with the end of our lives. It was great. There is a lot of good insight here. Like a lot of the non-fiction books I read, I really should go back and take notes on what I highlighted.

We’re all going to deal with death and sickness. This book can help.

Sapiens: A Brief History of Humankind by Yuval Noah Harari

My entire Goodreads review is two sentences.

This is an incredible book. You should read this. - Me

I still agree with this. My friend, Steve Deobald, described this book as “the most lucid book he’s ever read.” There is a reason this book has a 4.45 rating on Goodreads. Go read the blurb about it there and then buy and read this book1.

Essentialism: The Disciplined Pursuit of Less by Greg McKeown

If you don’t prioritize your life, someone else will. - Greg McKeown

A really great book encouraging you to focus on what matters and, as a result, make a bigger impact and be happier. It is better to make a mile of progress on one thing instead of making inches of progress in a bunch.

Tim Ferris recently published a podcast with Greg McKeown which I’d also recommend. I’ve enjoyed listening to the podcast after a bit of time away from the book. This has helped reinforce ideas from the book. If you’re hesitant to read the book, take the time to listen and pay attention to this long podcast.

I highlighted over 100 sections of this book. I plan on revisiting these notes and this book periodically.

Crucial Conversations Tools for Talking When Stakes Are High by Kerry Patterson, Joseph Grenny, Ron McMillan, Al Switzler

A crucial conversation is one where the stakes are high, opinions vary, and emotions run strong. This book provides guidance for handling those conversations better.

I enjoyed this book and thought I picked up some useful tips from it. I think this is another where doing follow up work would help solidify some of the concepts.

Rediscovering JavaScript: Master ES6, ES7, and ES8 by Venkat Subramaniam

Do you write JavaScript?

Did you write JavaScript in the past but then move on to languages like ClojureScript and miss all the changes that happened to JavaScript?

Both of those sentences apply to me. This book has been great at catching up on modern JavaScript. I find myself referencing it while writing JavaScript and it has been very helpful. It is to the point and I find myself referencing it periodically.

CivilWarLand in Bad Decline by George Saunders

I really like this book. It is a wonderful collection of short stories. This was my second time reading it and I still enjoyed it.

The Obelisk Gate (The Broken Earth #2) and The Stone Sky (The Broken Earth, #3) by N.K. Jemisin

N.K. Jemisin has won a Hugo three years in a row. Those three years line up with each release of a book in The Broken Earth series. They are really good.

This series is great. The world is interesting and the story compelling. I highly recommend it.

The Hate U Give by Angie Thomas

Reading lets you experience life from a different perspective. This book is good. It was quickly made into a movie which is also pretty good.

I read this as part of my book club and it was universally enjoyed.

Six of Crows (Six of Crows, #1) and Crooked Kingdom (Six of Crows #2) by Leigh Bardugo

I just really enjoyed this series. I enjoyed the fantasy world it was set in and have read most of Leigh Bardugo’s other books that are set in this same world.

The series is a young adult series. It isn’t complex. The reading isn’t difficult. It isn’t going to change your life and you’re not going to be blown away by the writing. It almost feels weird to include this series in the same list as CivilWarLand and The Broken Earth series. Even still, I found myself sucked into the story and didn’t mind spending the short amount of time it took to read the books.

Non-Five Star highlights

Life 3.0: Being Human in the Age of Artificial Intelligence by Max Tegmark

amazon, my highlights

I thought this was a really interesting book.

When: The Scientific Secrets of Perfect Timing by Daniel H. Pink

amazon, my review, my highlights

I really enjoyed this. Pink references other works to build a narrative about how timing matters. When should you take a nap? Is it better to go do the doctors in the morning or afternoon? How do are cognitive abilities generally change throughout the day? How should you try to end your vacations?

I did take some notes on the book while reading it and I have referenced them. It was a good book. I should have taken more notes.

Bloodchild and Other Stories by Octavia E. Butler

amazon, my review, my highlights

This is a great collection of short stories and non-fiction articles written by Octavia Butler. I really love her writing. I’ve read a few of her works and still enjoy Lilith’s Brood the most.

Below is a quote from her about science fiction that really resonated with me. It really hits home on one of the reasons I love reading science fiction.

But still I’m asked, what good is science fiction to Black people? What good is any form of literature to Black people? What good is science fiction’s thinking about the present, the future, and the past? What good is its tendency to warn or to consider alternative ways of thinking and doing? What good is its examination of the possible effects of science and technology, or social organization and political direction? At its best, science fiction stimulates imagination and creativity. It gets reader and writer off the beaten track, off the narrow, narrow footpath of what “everyone” is saying, doing, thinking—whoever “everyone” happens to be this year. And what good is all this to Black people? - Octavia Butler

Eeeee Eee Eeee by Tao Lin

amazon, my review

This book is real bizarre. For some reason I liked it.

Factfulness: Ten Reasons We’re Wrong About the World–and Why Things Are Better Than You Think by Hans Rosling

amazon, my review, my highlights

This book is great. It is very approachable and dispels some wrong common knowledge.

Stats

I struggled generating stats this year. I kept having data issues with Goodreads. There is data that is in Goodreads that is failing to export both through their export feature and API. I’m somewhat wondering what I would need to do to track reading in a different way.

Below is the reading stats per month. The numbers are based on when the book is completed. December is partially so low because the other books all carried over to January.

Book and pages count by month

Electronic books continue to make up the majority of the books I’m reading.

1
2
3
4
5
|           | 2018 | 2017 | 2016 | 2015 |
|-----------+------+------+------+------|
| ebook     |   37 |   37 |   56 |   47 |
| hardcover |    1 |    7 |    0 |    1 |
| paperback |    5 |    5 |    3 |    3 |

There are two physical books not included in my read books that I started and still need to finish. They are a both books focused on fitness (climbing injuries and proper movement) and aren’t books I’m actively reading.

Nearly a third of my reading was non-fiction. For the second year in a row, only two of those were software related.

1
2
3
4
|             | Number of books |
|-------------+-----------------|
| fiction     |              29 |
| non-fiction |              14 |

2019 Goals

I have a stack of software and process books and I’d like to read through at least some of them (others are more reference books). I’m also going to bring over the 2018 goal of reading at least one book on writing.

In a more general sense, I’m hoping to put some practices together that help me gain more from the books I’m reading. I’m still thinking through what that means.


  1. In the beginning of 2019 I also read Harari’s “21 lessons for the 21st Century.” Spoiler alert: this book will end up in my 2019 reading summary post.

Notifications with tmux and lein-test-refresh

I’ve been using Emacs in a remote tmux session lately and I’ve been missing lein-test-refresh notifications when my Clojure tests pass or fail. Luckily, it only took me a little bit of searching to figure out a solution for when I’m working inside of tmux.

Below is a GIF of the notifications I get as my tests run and pass or fail.

tmux and test-refresh notifications

With the above notifications, I can keep my focus on my code and only switch to the tmux window with lein test-refresh running when a test fails.

This was pretty easy to setup. You can trigger a message in tmux by running tmux display-message <MESSAGE_HERE>. To configure lein-test-refresh to send notifications to tmux simply include the following in your :test-refresh section of your project.clj or profiles.clj.

1
:test-refresh {:notify-command ["tmux" "display-message"]}

I hope you enjoy this. Its has made using a remote terminal with tmux and lein-test-refresh more enjoyable.

A more helpful makefile

In an older article of mine I extolled the virtues of having unified interfaces for interacting with your projects. I recently started working at Coinbase and the group I’m working with is mostly using makefiles as that common interface. We still have some more work to do to unify the makefile targets of the various projects but I’ve picked up one tip that makes switching between projects easier.

That tip is to have the default target of your makefile be one that prints out a helpful message. This looks like the following.

1
2
3
4
5
.PHONY: help
help:
  @grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
   sort | \
   awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

There is a lot going on there but it basically looks through your makefile targets and finds the ones that have a comment starting with ## after the target dependencies. Those targets are printed to the console along with the comment.

As an example, the makefile for my website looks similar to the below file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.PHONY: help
help:
  @grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
   sort | \
   awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

.PHONY: watch
watch: ## Watch for changes and serve preview of site with drafts
  bundle exec rake clean
  bundle exec rake preview

.PHONY: develop
develop: ## Serve a preview of the site without drafts and refresh changes
  bundle exec rake clean
  bundle exec rake develop

.PHONY: new_adventure
new_adventure: ## Start a new adventure post
  bundle exec rake new_adventure

.PHONY: new_post
new_post: ## Start a new post
  bundle exec rake new_post 

.PHONY: deploy
deploy: ## deploy
  ./deploy.sh

When this file, when I run make in this websites source, I get the following output.

1
2
3
4
5
6
7
0 [last: 0s] 21:11:50 ~/src/jakemcc/blog (master)
$ make
deploy                         deploy
develop                        Serve a preview of the site without drafts and refresh changes
new_adventure                  Start a new adventure post
new_post                       Start a new post
watch                          Watch for changes and serve preview of site with drafts

This is super useful when you’re starting doing work in a new project. With this feature you can get a quick list of useful targets and a description. It allows you to quickly see what can be done in a project.

Built-in test narrowing with lein-test-refresh

If you follow my work you probably know that I value fast feedback cycles. Most of the open-source I maintain was developed to enable faster feedback cycles. This is why lein-test-refresh and lein-autoexpect were originally created.

Leiningen supports test selectors and lein-test-refresh does as well. This lets you start-up a testing session and only run tests or namespaces with certain metadata on them. This is a super useful feature as it lets you narrow your testing scope to one (or a handful) of tests while working on solving a specific problem.

lein-test-refresh now has built-in functionality that allows you to focus your test scope without restarting the Leiningen test process. If lein-test-refresh sees a deftest or ns form marked with :test-refresh/focus true in its metadata, then it will only run tests marked with :test-refresh/focus.

Below is an example of what this looks like.

1
2
(deftest ^:test-refresh/focus test-addition
  (is (= 2 (+ 1 1))))

This functionality has only been available for a short period of time and I’ve already found it useful. I think you will too. Enjoy.

Tracking changes to a Reagent atom

I was recently having some difficulty debugging a problem in a ClojureScript single page application. The SPA was implemented using reagent1.

This interface stores most of its state in a global reagent.core/atom called db. To debug the problem, I thought it would be useful to track how the global state changed as I interacted with the interface. How do we do that?

For the rest of this article, pretend that (require '[reagent.core :as reagent]) has been executed.

First, let’s define db-history in the same namespace as the global reagent/atom, db. This is where we’ll collect the changes to db.

1
2
3
4
5
6
(ns ui.data
  (:require [reagent.core :as reagent]))

(defonce db (reagent/atom {:app/current-page :offer-list}))

(defonce db-history (atom []))

Next, let’s write a function called aggregate-state. This function grabs the current value in db and conjs it onto db-history. It also limits the history to the most recent 101 states.

1
2
3
4
5
6
(defn aggregate-state []
  (let [d @db]
    (swap! db-history (fn [hist]
                        (-> (take 100 hist)
                            vec
                            (conj d))))))

Now we need to invoke aggregate-state whenever db changes. We can do this using reagent/track. reagent/track takes a function and optional arguments and invokes that function whenever a reagent/atom that function depends on changes.

reagent/track! is similar except it immediately invokes the function instead of waiting for the first change. We can use it to cause aggregate-state to get called whenever db changes.

1
(defonce db-history-logger (reagent/track! aggregate-state))

Now history of the global state is being tracked. But we need a way to access it. Below is what I ended up writing. When you call ui.data.history() in Chrome’s JavaScript console, it returns an object you can click on to explore. If you pass in strings as arguments to history then it only selects some of the data from the global db and history.

1
2
3
4
5
6
7
(defn ^:export history [& args]
  (let [d @db
        k (if (seq args)
            (map keyword args)
            (keys d))]
    (clj->js {:history (mapv (fn [x] (select-keys x k)) @db-history)
              :current (select-keys d k)})))

It only took about fifteen lines of code to gain a view of our application’s state changes over time. This view helped me solve my problem. Hopefully it will help you too.


  1. This particular project is nearly four years old and has had many hands on it over the years. Working in it reminds me of how useful re-frame is on larger applications like this one.

Preventing duplicate long-running invocations in Clojure

A couple months ago I was looking into a problem and noticed that there was a situation where an expensive operation could be running simultaneously multiple times. This was wasteful.

This operation happened on a timer and could also be triggered by a power user through the UI. A power user could accidentally (or purposefully) mash on a UI button and cause the instance they’re interacting with to grind to a halt1.

It was pretty easy to prevent. All I needed to introduce was an atom and lean on compare-and-set!. compare-and-set! is a pretty neat function (and concept found in many languages). Here is the docstring:

Atomically sets the value of atom to newval if and only if the current value of the atom is identical to oldval. Returns true if set happened, else false

Basically, compare-and-set! changes the value of an atom only if it starts from a specified value and returns a boolean letting you know if it did.

To prevent an operation from running multiple times, introduce an atom and wrap calling the operation in a conditional using compare-and-set!. After doing the work, be sure to reset! your atom back to the starting value.

Below is the code.

1
2
3
4
5
6
7
8
9
10
11
12
(defonce running? (atom false))

(defn- expensive-operation!' []
  ;; do work
  )

(defn expensive-operation! []
  (when (compare-and-set! running? false true)
    (try
      (expensive-operation!')
      (finally
        (reset! running? false)))))

  1. OK, not really grind to a halt, but consume unnecessary resources.

Reading in 2017

I typically like to reflect on my previous years reading closer to the beginning of the next year. We are just entering March, so I’ve missed doing that.

Here are links to my previous end-of-year reflections: 2013, 2014, 2015, 2016.

I’ve continued to keep track of my reading using Goodreads. My profile continues to have the full list of the books I’ve read since 2010. Here is my entire 2017 record.

2017 Goal

My goal entering 2017 was to revisit some past favorites. I started this goal without setting a number, so I’ll just have to trust how I feel about it.

In 2017, I reread Frank Herbert’s Dune and John William’s Stoner. I also read new-to-me books by the authors David Foster Wallace, Haruki Murakami, George Saunders, and Neal Stephenson. I’ve also reread a George Saunders book in the first part of 2018.

I mostly achieved 2017’s goal. If I had reread another book, I’d consider it 100% completed, but I’m going to count reading some favorite authors towards the goal.

2017 Numbers

I read a total of 49 books for a total of 17,853 pages. I also read every issue of Amazon’s Day One weekly periodical1.

The number of five-star books I read this last year was low compared to previous years.

Recommendations

I only gave out seven five-star ratings. Two of the seven were books I reread. Title links are affiliate links to Amazon and the review links are to my review on Goodreads.

Below are more details on some of the above five-star books and some shoutouts for some non-five star books. Looking back over my books, I’d recommend any four-star or higher book without hesitation but am not going to put them all here.

Lilith’s Brood by Octavia Butler

Lilith’s Brood was one of the last books I read in 2017. It is a three book series published as a single book. It is amazing. This series achieves precisely what I want in a great science fiction book. I highly recommend this book. Reading this book reminded me why I love reading.

A quote from a non-fiction essay by Octavia Butler describes why good science fiction is fantastic.

But still I’m asked, what good is science fiction to Black people? What good is any form of literature to Black people? What good is science fiction’s thinking about the present, the future, and the past? What good is its tendency to warn or to consider alternative ways of thinking and doing? What good is its examination of the possible effects of science and technology, or social organization and political direction? At its best, science fiction stimulates imagination and creativity. It gets reader and writer off the beaten track, off the narrow, narrow footpath of what “everyone” is saying, doing, thinking—whoever “everyone” happens to be this year. And what good is all this to Black people? - Octavia Butler

The Sense of Style by Steven Pinker

Yes, I read a book on writing and think this is one of the top books I read last year. I initially read a Kindle edition from my local library and then immediately bought the hardcover so I can reference it while writing.

The writing is great. The book is humorous. I’d highly recommend to anyone that writes. I should reread this.

Dune by Frank Herbert

Dune is a classic for a reason. It was still great my second time through it. If you haven’t read Dune, you are missing out.

If you read it on a Kindle, I have a custom Kindle dictionary that makes reading it more pleasurable.

Stoner by John Williams

It is still unclear to me why I like this book so much, but I do. The writing is crisp. The story is depressing.

Stories of Your Life and Others by Ted Chiang

Over the years I’ve started to enjoy reading short story collections. Every story in this collection was great. I devoured this book and then everything else I could find by Ted Chiang.

Capital in the Twenty-First Century by Thomas Piketty

This is a massive book. It probably deserved five-stars. It presents a ton of information to the reader. It is boring. It also made me think about the role of taxes in society and changed my thoughts about them.

If you’ve been putting this off, you can probably skip to the last section and still get a lot from this book.

Here is a review that does a spot on job of describing the book. Here is an Amazon link and my own review.

Bobiverse Series by Dennis Taylor

This is a fun light-hearted science fiction series. It still manages to explore some deep topics. Read the description and if it sounds interesting to you, pick it up.

Stats

Similar to last year, April and September were times when I wasn’t reading a ton.

Chart of reading per month

This year physical books made a comeback. I checked out more physical books from the library this year than in the past.

1
2
3
4
5
|           | 2017 | 2016 | 2015 |
|-----------+------+------+------|
| ebook     |   37 |   56 |   47 |
| hardcover |    7 |    0 |    1 |
| paperback |    5 |    3 |    3 |

My average rating went down a bit.

1
2
3
4
5
6
7
8
9
| Year | Avg Rating |
|------+------------|
| 2011 |       3.84 |
| 2012 |       3.66 |
| 2013 |       3.55 |
| 2014 |       3.49 |
| 2015 |       3.86 |
| 2016 |       3.85 |
| 2017 |       3.77 |

I read a lot of non-fiction books this year. Only two of them were directly related to software.

1
2
3
4
|             | Number of books |
|-------------+-----------------|
| fiction     |              30 |
| non-fiction |              19 |

2018 Goals

There are a few more books on writing that I’ve wanted to read for a while. I’m planning on reading at least one of them this year. I’m also want to read more Octavia Butler.


  1. Unfortunately, this periodical has ended after years of publishing once a week. I’m bummed. I really enjoyed receiving a short story and poem once a week.

Creating serverless applications with ClojureScript and Firebase

Earlier this year, I traveled to India and gave a presentation at IN/Clojure. I talked about building serverless ClojureScript applications that use Firebase to persist and sync data between clients.

I was pretty pleased with how the talk went. The people who talked to me after seemed to enjoy the presentation and were inspired to try out some of the techniques and tools I mentioned.

Here is the talk. I hope you enjoy it. It was fun to give.

Using Clojure macros for nicer error handling

In July 2017, I found myself editing some Clojure code that looked approximately like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(defn validate-required-fields [params]
  (when-not (contains? params :source)
    "Missing source field"))

(defn validate-invariants [params]
  (when (>= (:lower params) (:higher params))
    "lower field must be smaller than higher"))

;; route handler taken out of other routes
(GET "/event-redirect/:event_type" request []
  (let [params (:params request)]
    (if-let [field-error (validate-required-fields params)]
      {:status 400 :body field-error}
      (if-let [invariant-error (validate-invariants params)]
        {:status 400 :body invariant-error}
        (publish-and-redirect params)))))

This route handler validates its inputs, and if they fail validation, then it returns an error response. I found this pretty ugly. This small chunk of code has numerous if branches and quite a bit of nesting. All of this makes it hard to read and hurts understanding.

While adding a new feature to it, I remembered some code I wrote with Case back in late 2015. Back then we were working on Lumanu and wrote a Clojure macro that we called halt-on-error->>. This macro worked similarly to ->>, except it allowed any step in the processing pipeline to halt execution and trigger an error handler. We were working on a web crawler at the time, and this macro significantly improved the readability of our data processing pipeline. There was a lot of error handling code throughout the web crawler, and this macro helped keep it readable.

I realized that using a similar macro would make this code easier to follow. I recreated halt-on-error->> to allow any form to cause it to return early. The above code could then be written like below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(defn validate-required-fields [params]
  (if (contains? params :source)
    params
    (exec/halt {:status 400 :body "Missing source field"})))

(defn validate-invariants [params]
  (if (< (:lower params) (:higher params))
    params
    (exec/halt {:status 400 :body "lower field must be smaller than higher"})))

(GET "/event-redirect/:event_type" request []
  (exec/halt-on-error->> request
                         :params
                         validate-required-fields
                         validate-invariants
                         publish-and-redirect))

Once you understand halt-on-error->>, this chunk of code is much easier to read.

Let’s implement halt-on-error->>.

Implementing halt-on-error->>

Here are some tests for that specify how halt-on-error->> should work.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
(ns halt.execution-test
  (:require  [halt.execution :as exec]
             [clojure.test :refer :all]))

(def produce-error (constantly (exec/halt {:x "foobar"})))

(defn success-fn
  "Weird function that appends suffix to s"
  [suffix s]
  (str s suffix))

(deftest single-step
  (is (= "first" (exec/halt-on-error->> (success-fn "first" "")))))

(deftest two-steps-with-no-error
  (is (= "firstsecond" (exec/halt-on-error->> (success-fn "first" "")
                                              (success-fn "second")))))

(deftest error-as-first-step
  (is (= {:x "foobar"} (exec/halt-on-error->> (produce-error))))
  (is (= {:x "foobar"} (exec/halt-on-error->> (produce-error)
                                              (success-fn "first")))))

(deftest error-after-first-step
  (is (= {:x "foobar"} (exec/halt-on-error->> (success-fn "first" "")
                                              (produce-error)
                                              (success-fn "second")))))

(deftest works-with-anonymous-functions
  (is (= 1 (exec/halt-on-error->> (success-fn "first" "")
                                  ((fn [x] (exec/halt 1)))))))

Below is an implementation of halt-on-error->>.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(ns halt.execution)

(defrecord Stopper [x])

(defn halt [data]
  (Stopper. data))

(defmacro halt-on-error->> [form & forms]
  (let [g (gensym)
        pstep (fn [step] `(if (instance? Stopper ~g) ~g (->> ~g ~step)))]
    `(let [~g ~form
           ~@(interleave (repeat g) (map pstep forms))]
       (if (instance? Stopper ~g)
         (.x ~g)
         ~g))))

So what is this macro doing? First, it uses gensym to get a symbol with a unique name and stores this in g. It then defines a helper function called pstep for use in the code generation part of the macro.

This macro generates a let block that repeatedly executes a form and assigns the return value back to g. g is then checked to confirm execution should continue before it is threaded into the next form. If g is ever an instance of a Stopper, execution halts and the value wrapped in the Stopper is returned.

Looking at an expanded version of a macro can be easier to understand than a written explanation. Below is a macroexpanded version of one of the tests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
;; What is being expanded
(macroexpand-1 '(exec/halt-on-error->> (success-fn "first" "")
                                       (produce-error)
                                       (success-fn "second")))

;; The expansion
(let [G__15365 (success-fn "first" "")
      G__15365 (if (instance? halt.execution.Stopper G__15365)
                 G__15365
                 (->> G__15365 (produce-error)))
      G__15365 (if (instance? halt.execution.Stopper G__15365)
                 G__15365
                 (->> G__15365 (success-fn "second")))]
  (if (instance? halt.execution.Stopper G__15365)
    (.x G__15365)
    G__15365))

Looking at that expansion, you can see how we are using a let block to repeatedly assign to the same symbol and we check that return value before executing the next stop.

This isn’t a new pattern. There are libraries that implement similar ideas. At IN/Clojure 2018, Varun Sharma gave a talk about how this cleaned up their code. You can even get bogged down and throw around words like monad when talking about it.

I’d encourage you to look at your code and see if you have areas where error handling code is detracting from the readability. This might be an area where this, or something similar to it, would help.

Parsing multiple date formats with clj-time

I recently needed to optimize the speed of some Clojure code. After investigating, I identified that a huge number of exceptions were being thrown and handling these was slowing down the process.

The code throwing the exceptions was parsing strings into Joda-Time DateTime objects using the clj-time library.

The code was calling clj-time.coerce/from-string which calls clj-time.format/parse. format/parse iterates through up to approximately 50 formatters in an attempt to parse whatever string you pass it. If one of these formatters doesn’t parse the string, it throws an exception which format/parse catches and ignores before attempting the next formatter.

This was pretty wasteful. This was especially wasteful in the code I was working in since it only needed to handle two different date formats.

Luckily, Joda-Time has a way to build a formatter that handles multiple formats and clj-time provides access to it. Below is code that creates a formatter that handles two different formats.

1
2
3
4
5
6
7
8
9
10
11
(ns multiple-dates.core
  (:require [clj-time.core :as time]
            [clj-time.format :as time-format]))

(def multi-format
  (time-format/formatter time/utc
                         "YYYY-MM-dd"
                         "YYYY-MM-dd'T'HH:mm:ss.SSSZ"))

(defn parse [s]
  (time-format/parse multi-format s))

And below are some examples of using it in the repl.

1
2
3
4
5
6
7
8
multiple-dates.core> (parse "2017-09-04")
#object[org.joda.time.DateTime 0x5d5e4cd7 "2017-09-04T00:00:00.000Z"]

multiple-dates.core> (parse "2017-09-04T12:11:02.123Z")
#object[org.joda.time.DateTime 0x174f3a5c "2017-09-04T12:11:02.123Z"]

multiple-dates.core> (parse "2017-09-04-12:11:02.123Z")
IllegalArgumentException Invalid format: "2017-09-04-12:11:02.123Z" is malformed at "-12:11:02.123Z"  org.joda.time.format.DateTimeFormatter.parseDateTime (DateTimeFormatter.java:945)

Looking back at that code, it seems pretty straightforward. I’ll admit that it took me and my pair a while to figure out how to do this using clj-time. I ended up looking at Joda-Time’s documentation and implemented this using Java interop before I cracked how to use clj-time.format/formatter to do the same thing.