PurelyFunctional.tv Newsletter 348: Tip: cleanup in finally
Clojure Tip 💡
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
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
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.
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
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
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
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
(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
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
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
what it looks like:
(with-open [writer (io/writer "my-file.txt")] (doseq [x (range 100)] (.write writer x) (.write writer "\n")))
.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:
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
If you need to run an action on all elements of a collection, you can
(run! println (range 100))
This will run all of the
printlns in sequence. But there's no built-in
function to run all of those functions in parallel. That's your task
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
(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.