try-let is a Clojure macro designed to make handling some exceptions slightly nicer. It acts like let, but allows you to catch exceptions which may be thrown inside the binding vector. Exceptions thrown inside the body of the try-let are deliberately ignored.
try-let is in Clojars. To use it in a Leiningen project, add it to your project.clj dependencies:
then require try-let in your code:
(ns my.example
(:require [try-let :refer [try-let]]))It can be quite difficult to combine try/catch with let properly. Clojure pushes you towards one of two patterns, neither of which is ideal.
(try
(let [value (func-that-throws)]
(act-on-value value))
(catch Exception e
(log/error e "func-that-throws failed")))In the above pattern, the scope of the try/catch is too great. In addition to func-that-throws, it also affects act-on-value.
(let [value
(try (func-that-throws)
(catch Exception e (log/error e "func-that-throws failed")))]
(act-on-value value))In the above pattern, the scope of the try/catch is correct, affecting only func-that-throws, but when an exception is caught, act-on-value is evaluated regardless and must handle the exceptional case when value is nil.
With try-let, we can instead do:
(try-let [value (func-that-throws)]
(act-on-value value)
(catch Exception e
(log/error e "func-that-throws failed")))This allows the scope of the try/catch to be made as precise as possible, affecting only func-that-throws, and for evaluation to only proceed to act-on-value when value is obtained without error. In this way, try-let can be thought of as similar to if-let, where the body is only evaluated when the value of the binding vector is not nil.
You can have multiple catch stanzas for different exceptions. Much of what you'd expect to work in a normal let works:
(try-let [val-1 (risky-func-1)
val-2 (risky-func-2 val-1)]
(log/info "using values" val-1 "and" val-2)
(* val-1 val-2)
(catch SpecificException _
(log/info "using our fallback value instead")
123)
(catch RuntimeException e
(log/error e "Some other error occurred")
(throw e))
(finally
(release-some-resource)))As an alternative, you can also put catch stanzas before other body expressions:
(try-let [val-1 (risky-func-1)]
(catch Exception e
(log/error e "Problem calling risky-func-1")
0)
(try-let [val-2 (risky-func-2 val-1)]
(catch Exception e
(log/error e "Problem calling risky-func-2")
0)
(log/info "using values" val-1 "and" val-2)
(* val-1 val-2)))This makes the code logic more linear, where exceptions are handled closer to where they appear.
There is also a try+-let macro which is compatible with slingshot-style catch stanzas.
Copyright © 2015-2019 rufoa
Distributed under the Eclipse Public License, the same as Clojure.
