PurelyFunctional.tv Newsletter 347: Tip: catch and catch again
Issue 347 - October 07, 2019 · Archives · Subscribe
Clojure Tip 💡
catch
and catch
again
Special thanks to Richard Barker for this tip!
Let's continue on our theme of exception handling. You should use the
catch
statement's built-in dispatch to have different behavior for
different exception types.
Richard mentioned seeing code that catches an Exception
then checking
its type to decide on its behavior. I've seen this myself. Here's an
example:
(try
(clojure.java.io/reader "/tmp/file.txt")
(catch Exception e
(if (instance? java.io.FileNotFoundException e)
(println "File not found")
(throw e)))) ;; unknown exception so re-throw
The catch
expressions in a try
have built-in type-based dispatch, so
this extra instance check is redundant, and quite possibly many times
less efficient, than the following code:
(try
(clojure.java.io/reader "/tmp/file.txt")
(catch java.io.FileNotFoundException e
(println "File not found")))
The reason the catch
expression starts with a class is because it is
looking to match only instances of that class. You can have multiple
catch
expressions to have different behavior for different kinds of
exceptions.
If you can have multiple catch
expressions, then there must be some
rules for choosing which one applies. The rules are simple. The JVM
checks each catch expression, in order. If the expression in question is
an instance (direct instance or instance of a subclass) of the given
class, then it's a match. The search stops, and the code is run. If the
JVM gets to the end and no match was found, the exception is not caught
and it continues to bubble up the stack (to the code that called this).
That's why we don't need to re-throw the exception. We are selecting
only FileNotFoundException
s. All other exceptions pass through.
These rules lead to a good rule of thumb: catch the most specific
exception first. If you have multiple catch statements, they are checked
in order. That means that if you have a superclass before its subclass,
the subclass's catch
will never fire. Let's make it concrete:
(try
(clojure.java.io/reader "/tmp/file.txt")
(catch java.io.IOException e
(println "Some kind of IO Exception"))
(catch java.io.FileNotFoundException e ;; this will never catch!!!
(println "File not found")))
Because FileNotFoundException
is a subclass of IOException
,
IOException
will always match for FileNotFoundException
s. Reverse
the order to put the most specific one first.
Book update 📖
The book is selling well, according to the publisher. They are hard at work getting Chapter 4 ready. I imagine it will be released any day now.
Meanwhile, I'm working on Chapter 6, which is all about immutability. Believe it or not, but JavaScript is a great language for teaching immutability. Because it's not the default, it's something to call out all the time. You have to think about it more and bring it up in the text. If it happened by default with no effort, it would seem like a magical feature of the language.
I have a very pragmatic take on immutability. I define immutability as "this cannot change because there is no code that changes it" instead of as "this cannot change because we've prevented any code, existing or not, from changing it". This lets you do immutability in JavaScript without special features like freezing. I talk about this in a podcast episode on immutability.
This makes immutability a discipline instead of a language feature. I hope that the difficulty of applying the discipline will encourage people to check out languages that are immutable by default.
You can buy the book and use the coupon code TSSIMPLICITY for 50% off.
Currently recording 🎥
Woohoo! I did it! After several attempts and lots and lots of preparation, I recorded and published the lesson about Testing Distributed Systems.
In the lesson, we develop tests for a distributed key-value store that I wrote. Even though I had rehearsed the lesson before recording, running the code many times, I still found a bug in the key-value store client during the recording. Distributed systems are hard!
We start off with an easy test to make sure the system is eventually consistent. Then we develop a model-based test of the system. The challenges are many: it's asynchronous, we can't introspect into the server, and the outcome depends on the order the clients sync to the server. There's lots of indeterminacy in the system, and we have to model that with a set of possible states. When that set is empty, it means there's no way to get what we have (according to the model), so we have a failing case.
Along the way, we learn lots of lessons, like how to ensure we get reliable failing cases, how to test our tests at the REPL, and how to interpret our results.
Property-based Testing course is winding down now. There are several more lessons planned, including how to test a system that doesn't use Clojure, Spec integration, and how to use PBT during design to make sure you don't violate any invariants.
BTW: the only way to get the course now is to get a membership. Do it!
Brain skill 😎
autonomy
If you're reading this newsletter, you're probably a fairly autonomous learner. Even still, it's worth exploring how autonomy helps us learn.
Autonomy in learning means that we are able to take charge of our own learning. Unfortunately, many of us were not given much autonomy in the years of schooling we went through. The next lesson, assignment, or project was chosen for us. Even when we were given a choice, we weren't taught how to make that choice.
Once you're out of school, you're on your own. What should you learn? What do you want to learn? Why do you want to learn it? These are all questions that help us explore our autonomy. Yes, in the short term, learning on our own is less efficient. It's so much more direct to be given exactly what you should learn next. But this assumes the teacher assigning it to you knows exactly what you need. Who can give such personalized attention? Maybe a personal tutor, but not a teacher with a class of 20-40 students.
No, we have to take charge of our own learning, even though we will go down dead ends and make wrong turns. In the long run, it is more efficient. We learn things that relate to a sense of purpose. We learn things we are curious about. We learn when we are ready (not at the ring of a bell). And we learn the way we learn best.
How can you develop this skill? You must learn to set goals, following you motivation and finding purpose. You must have the self-awareness to know your own learning process. And you must be able to plan your learning by applying appropriate learning strategies.
But, like I said, if you're reading this, you're probably ahead of the autonomy curve. You did choose to receive this newsletter, which already shows a lot of initiative.
Clojure Challenge 🤔
Last week's challenge
The challenge in Issue 346 was to make a higher-order function that implements exponential backoff.
You can check out the submissions here.
This week's challenge
delete from a vector
Clojure's vectors let you efficiently get a value given an integer index. Often, I want to remove a value from a vector at a certain index. This doesn't exist in the core library, so I wind up writing it each time. The question is how to do it in an efficient way?
Here's the task: write a function delete-at
that takes a vector and an
index and returns a new vector with the item at the index removed. The
new vector should be one shorter than the argument.
(defn delete-at [v i]
;; your code here
)
Please be mindful of the efficiency of your solution.
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