Elixir is dynamic and has a pretty different approach to this, clojure can learn about it
Weâve discussed this before here Error handling in clojure
In that thread, we werenât able to actually identify what problems Elixir solved in its handling of exception over Clojureâs. Thus I still stand by the fact theyâre both just as good and it comes down to personal preference and familiarity.
I welcome reviving the conversation if you have new datapoints of example cases that are problematic to handle in Clojure, but not in Elixir.
Regards
I would guess that the foremost reason clojure is fine with exceptions is that itâs hosted on runtimes that all use exceptions fairly liberally. My feeling is that exceptions tend to be slightly over-used but thereâs nothing fundamentally bad about them - itâs useful to have a âbail outâ mechanism that works its way up the callstack by default.
The one thing I havenât seen mentioned in this thread is the Common Lisp extension/alternative of the condition/restart system. Itâs a mechanism to allow code up the call stack to decide on what to do in an âexceptionalâ condition, which gets to pick from a number of strategies provided by the callee. When a condition like that works its way up to the REPL the user/programmer can even pick a strategy at that point and the program can just continue. Pretty magical when you run into it first.
There are a few implementations of the mechanism in the Clojure ecosystem but none have caught on. Iâm not really sure why not.
There is an abstraction that is less powerful than a Monad but which already makes your life better: Functors.
Imagine you want to write a function that adds 3 to its argument. Easy.
Now however assume that the argument could be nil in which case you just want to reply with nil.
(defun foo1 (n)
(when-not (nil? n)
(+ n 3)))
And now letâs say that you want to write a similar function where you have a list of numbers and want to add 3 to each of them:
(defun foo2 (nums)
(map #(+ % 3) nums))
Or what if you want to write a function to which you pass another function which either returns a number or which throws an exception?
(defun foo3 (f)
(try
(+ (f) 3)
(catch e ...)))
Or you have a hashmap in which all the values are numbers and you want to add 3 to each of them and thus generate a new map:
(defun foo4 (hm)
(zipmap (keys hm) (map #(+ % 3) (vals hm))))
In Haskell you would be write an implementation against Functors. In Clojure they exist too, as well as monads. So in Haskell you would express it like this:
foo = fmap (+3)
And in Clojure you could maybe say:
(def foo (partial fmap #(+ % 3)))
And thatâs it.
This foo can potentially replace all of the foo versions above. So it is much more reusable. Composability is the holy grail of functional programming and monads compose very well.
We should call out Scott Wlaschinâs amazing series of blog posts and presentations on this topic (amongst many!) which he terms Railway Oriented Programming (ROP). His focus is F# and typed functional languages, and so a little purist compared to whatâs required in Clojure. But the principles of the benefits of composition etc are really nicely illustrated. https://fsharpforfunandprofit.com/posts/recipe-part2/
In our team weâve used Adam Bardâs err->> macro verbatim for a long time. More recently a team member - inspired by Scottâs articles - wrote a Clojure version of ROP. This started off following Scottâs patterns slavishly but then evolved to be more idomatic Clojure. We use both in our code now. https://github.com/HughPowell/railway-oriented-programming
Both show how Clojure can be adaptable to these patterns in a few lines of code without explicitly needing much of the functional apparatus like âmapâ and âliftâ that Scott Wlaschin so nicely defines in his (algebraically) typed FP landscape.
Completely agree with other posters that adoption of this pattern is a choice - there should be no dogma about âgoodâ or âbadâ Clojure or FP. However I found this area to be one of those ways into the FP world that I enjoyed and which has brought benefits to our team - i.e. the ease of composition and reduction of magic code paths that comes about with exception habdling.
Note one titbit of advice might be to not catch every exception. If something is vaguely recoverable catch and pass the error down the (monadic) chain - but throw exceptions for truly unexpected and unrecoverable cases. Again probably too pragmatic for some!
This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.