Skip to content
/ try-let Public

Better exception handling for Clojure let expressions

Notifications You must be signed in to change notification settings

rufoa/try-let

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

try-let

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.

Build Status

Installation

try-let is in Clojars. To use it in a Leiningen project, add it to your project.clj dependencies:

Clojars Project

then require try-let in your code:

(ns my.example
   (:require [try-let :refer [try-let]]))

Motivation

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.

Use

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.

Slingshot support

There is also a try+-let macro which is compatible with slingshot-style catch stanzas.

License

Copyright © 2015-2019 rufoa

Distributed under the Eclipse Public License, the same as Clojure.

About

Better exception handling for Clojure let expressions

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published