PurelyFunctional.tv Newsletter 348: Tip: cleanup in finally
Issue 348 - October 14, 2019 · Archives · Subscribe
Clojure Tip 💡
cleanup in finally
There's a common pattern in code, where you need to create a resource, use it, then clean up the resource.
(let [resource (create-resource)]
(use-resource resource)
(cleanup-resource resource))
You can imagine the resource is anything that might need some cleanup. It could be opening a file, reading from it, then closing the reader. Or a database connection. Or a thread pool.
After a while, we may realize that the function use-resource
could
throw an exception. We want to catch that exception, so we do this:
(let [resource (create-resource)]
(try
(use-resource resource)
(cleanup-resource resource)
(catch Exception e
(handle-exception e))))
This looks good, but unfortunately there's a problem. If use-resource
throws an Exception, it will skip the cleanup. We could copy the
cleanup-resource
call also inside the catch:
(let [resource (create-resource)]
(try
(use-resource resource)
(cleanup-resource resource)
(catch Exception e
(handle-exception e)
(cleanup-resource resource))))
This will work, but there's a better way built into Clojure and the JVM.
The try
expression allows you to add a finally
clause that is always
run when exiting the try
expression. It's run regardless of how the
try
expression is exited. If the body is successful, the finally
clause is run. And if any of the catch
statements, or none of them,
are run, the finally
is also run.
This is what it looks like:
(let [resource (create-resource)]
(try
(use-resource resource)
(catch Exception e
(handle-exception e))
(finally
(cleanup-resource resource))))
Another nice thing is that you can use the finally
clause even without
a catch
. That is, you can ignore exceptions (let them slip through)
but still have safe cleanup of resources.
You might be wondering about what happens with the value of a try
expression if you use a finally
. Well, there's no need to worry. The
value of a try
is unaffected by having a finally
. The value of the
last expression in the try
's body is the value of the try
. Here's an
example:
(defn fetch-user [userid]
(let [db (db/connect)]
(try
(db/fetch db "user" userid)
(finally
(db/disconnect db)))))
db/fetch
returns the results of a query. It's the last expression in
the try
, so the result of the query is returned from the function
fetch-user
(or it throws an exception, of course).
One more thing: if you are opening a file or a stream, you could write this:
(let [writer (io/writer "my-file.txt")]
(try
(doseq [x (range 100)]
(.write writer x)
(.write writer "\n"))
(finally
(.close writer))))
That write the numbers 0 through 99 to a file using the try/finally
pattern we just went over. However, Clojure comes with a macro that does
that specific version of the pattern. If the cleanup you do in a
finally
is just to call the .close
method, you should use
with-open
. Here's
what it looks like:
(with-open [writer (io/writer "my-file.txt")]
(doseq [x (range 100)]
(.write writer x)
(.write writer "\n")))
The try
, finally
, and .close
are handled for you. If you're
interested in how the with-open
macro can be written, I've got a
lesson on it called Macro example:
with-open.
Book update 📖
Chapter 4 of Grokking Simplicity is out! Chapter 4 is about refactoring your actions to extract out calculations. By making code calculations, you make it easier to test and reuse.
You can buy the book and use the coupon code TSSIMPLICITY for 50% off.
I've been getting such positive messages about the book. Thank you so much for reading it. It's hard work and those nice messages help me keep going.
PurelyFunctional.tv Update 💥
A couple of weeks ago, I added a long-missing feature to PurelyFunctional.tv. Before, if you bought a Team License to a course, the only way to share it with your team was to download it and share the files. That wasn't very convenient.
Now, you can share your Team License purchases with other users on the site, so they can view them online. You can add people to your team by their email addresses. Just visit your dashboard and enter their emails. They'll then have access to the courses you've bought with a Team License.
Currently recording 🎥
This week, there's a new, much anticipated lesson. This one is all about Clojure Spec integration. In this lesson, we go over how to access the automatically created generators for Specs and how to replace them with custom generators when needed.
Go check it out here: Testing with Spec: custom generators
Up next? How to test functions with Spec. We'll be exploring the limits of the testing we can do in Spec. Spoiler alert: it's quite powerful.
BTW, Magnus Kvalevåg has a cool presentation on stateful property based testing at Clojutre.
Clojure Challenge 🤔
Last week's challenge
The challenge in Issue 347 was to write a function to efficiently remove a value from a vector, given its index in the vector.
You can check out the submissions here.
I tried a variety of implementations and did some primitive benchmarking of them. I need a more thorough system to know which one is the fastest.
Alan Thompson points out that Tupelo has this function already.
This week's challenge
parallel execution
If you need to run an action on all elements of a collection, you can
use [run!
](http
://clojuredocs.org/clojure.core/run!).
(run! println (range 100))
This will run all of the println
s in sequence. But there's no built-in
function to run all of those functions in parallel. That's your task
this week.
Write a function runp!
that takes a function and a sequence of
arguments. It will run the function on different threads and wait for
them all to finish. It returns nil
, just like run!
.
(defn runp! [f coll]
Please be mindful of the number of threads it creates. For instance, if I create 4000 threads on my machine, it crashes the OS. The typical solution is to use a thread pool.
As usual, please send me your implementations. I'll share them all in next week's issue. If you send me one, but you don't want me to share it publicly, please let me know.
Rock on!
Eric Normand