Erlang/Elixir style of returning 2 elements with :ok / :error status

Dear Clojuverse community,

Last year I started using my first functional language Elixir and this year I decided to try Clojure and write some serious amount of code in it to make a decision which I like more.

While reading Getting Closure book I started re-writing some snippets of code that I wrote in Elixir and I quickly realized that in Elixir I extensively used an interesting pattern that technically can be implemented in Clojure but I’m not sure that this would be a good idea.

In Elixir many functions return 2 element tuple {:ok, "something"} or :error / {:error, :description_what_happened} which in Clojure would look like 2 element vector [:ok, "something"] or [:error, :description_what_happened].

Of course in Elixir you’re not forced to return something like that but since a lot of core functions do return it so it makes totally sense to write your own functions that return :error instead of throw when something is not all right.

There are a couple of reasons why Erlang/Elixir in my opinion is pushing this pattern:

  1. You are not supposed to use try/catch (except when using non-native libraries). You can throw but you don’t use it often and if you do, the naming convention is to add “!” to highlight that the function can explode.

  2. You use pattern matching literary everywhere so you end up writing a lot of code like this:

case do_something(with_this) do
  # you can for example just return it
  {:ok, data} -> data

  # or do something
  {:error, :valve_is_broken} -> ...somehow handle broken valve...

  # or do something with error you're not prepared for
  {:error, error} -> log_this_error(error)
end

Could you please tell me does this 2 element OK/ERROR pattern have a use in Clojure?

Of course it does not have to be vector. I think a Map {:status :ok, :something "something"} makes more sense since you can easily add keys without braking API (as mentioned in Should I use vector to return 2 or 3 pieces of information from a function?).

I think I’m also slightly confused about error handling since the Clojure answer might be to just return nil instead of throw.

Thank you.

Kind regards,

Allan

2 Likes

IME after building some toy apps with Elixir, and using Clojure professionally for a few years now, I think that you can certainly replicate this pattern in Clojure but it will be less useful for a few reasons:

First, Elixir’s pattern matching has a lot of mechanical sympathy with returning a tuple as a result, as you lay out in your post. Clojure does not have pattern matching in its core library, although you can adopt a library like core.match.

Second, one of the reasons that Elixir takes throwing exceptions so seriously is because of the way it models programs as independent, concurrent processes. When an exception occurs in a process, often it’s not enough to simply catch and handle the error - very often you need to communicate to another process that an error occurred. This makes it far less ergonomic to use exceptions as you probably need to catch it and turn it into a message at the boundary of your process anyway, and your process is already probably handling these messages from other processes, so why not unify the way errors are handled intra- and interprocess?

Clojure, on the other hand, lives in the JVM where programs are typically colocated in memory and you can rely more on an exception being caught and handled at the point in the call stack where it’s relevant.

Finally, as you alluded to, Clojure’s use of nil punning goes a long way to replacing the use of tuples for easily recoverable situations (i.e. operating on some data that is missing some keys or values) that shouldn’t result in a program failure. Clojure’s core library has a lot of mechanical sympathy with this approach and provides a number of tools for gracefully handling nils like if-some, some-> and others.

That being said, there’s libraries and articles outlining how to do this in Clojure. I wouldn’t consider it idiomatic, but if you’re working on a project where you’d like to try and no one is stopping you, why not? :smiley: A good overview of the approach in Clojure I found is this article: https://medium.com/appsflyer/railway-oriented-programming-clojure-and-exception-handling-why-and-how-89d75cc94c58

1 Like

Thank you @lilactown for the link. It’s very good explanation.

The macro mentioned in the article:

(=>> request
  decode-request
  validate-user-input
  enrich-input
  update-DB
  send-email
  build-response-with-status)]

is very similar to with in Elixir which is also a macro:

with {:ok, foo}  <- get_something(something),
      {:ok, bar} <- convert_to_something1(bar),
      {:ok, bar} <- convert_to_something2(bar, foo),
      bar        <- something_that_cannot_fail(bar),
      {:ok, bar} <- convert_to_something3(bar, foo) do
  {:ok, bar}
else
  {:error, _} = error -> error
end

with is more complicated but more powerful and it’s one of those tools in Elixir you need to use very often.

I’d suggest reading this: Error handling in clojure - #5 by didibus

try/catch with exceptions and Elixir with macro with pattern matching is basically similar in ergonomics.

I will say that it does have a use in very specific scenarios, but mostly not. I’d keep to throwing, or returning nil/false depending on what you want to communicate. It’s how most of Clojure is designed, so it’ll fit well within the rest.

I think the best way to achieve pattern like this in Clojure is vector with 2 elements - boolean status and data/error description. Using Clojure’s destructuring syntax you can then easily get these values like this:

(let [[ok? result] (do-something)]
  (if ok?
    (str “Ok - “ result)
    (str “Failure - ” result)))

You can even wrap it in simple macro like this for example:

(if-let-ok? result (do-something)
  (str “Ok - “ result)
  (str “ Failure - “ result))

Whether this approach is good or not already have been said. IMO if you don’t use libraries like Failjure and want to avoid throwing exceptions to “be pure” it can be helpful

Thank you @didibus

I read both Error handling in clojure and Is error-throwing bad functional programming? and your explanation

is the best btw.

1 Like

Zach Tellman’s “manifold” library (aleph) extensively uses a clever example of this technique in the context of asynchronous operations (where throwing an exception gets you nowhere). Check out the Manifold “Deferred” page. It is very much like a 2-item vector [:ok 42] but it is a record type, in order to implement the “@” or deref function as an idiomatic get-the-ok-value-or-throw. There’s a bunch of operations to chain functions that return Deferred (and cam out as soon as one of them returns a not-ok value). The cherry on top is let-flow, a thrilling and fun-to-read macro that hides the plumbing. Altogether, the “Deferred” ns makes good reading.

1 Like

A lot of great reading. THANK YOU

Thank you. I found that one and also Promenade and Failjure

I think at this time I’ll just keep it simple without using additional libraries until I understand / know that I really need something extra.

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.