cjbottaro
What do you think about early returns?
I’ve been primarily doing Elixir development for the past 6 years or so, and during that time whole heartedly committed to functional paradigms.
But recently I did some Go programming, and I hate to admit it, but working with early returns again was kinda nice.
I’m not a fan of lots of small little functions, and using with seems to necessitate that; a lot of the times I’ll just deal with case/if nesting.
So one day, I threw (pun intended) in the towel and refactored a plug that has many success cases as well as error cases that need to return early, to use throw/catch to emulate early returns… and I was really happy with the results.
But there is this nagging feeling that I’ll be excommunicated from the community if this code ever sees the light of day publicly. I kid, I kid… ![]()
So why are early returns bad? I’ve since wrapped up the throw/catch paradigm into a tiny library that let’s it be used like this:
v = returnable do
if some_condition?()
return "foo"
end
...
end
I understand that early returns make mechanical “refactor into function” difficult, but I think wrapping it up in an expression like above negates the issue. Not sure.
I’ve seen some posts (on Reddit, not here) where people suggest you can use throw/catch to emulate early returns “but you need to be an expert to do it safely and properly.” Why is that? What pitfalls are they alluding to?
As always, thanks for the help and info!
P.S. A little further in that video, he describes a use block that he wishes existed, but doesn’t know any programming language that has something like. I’m nearly positive it can be accomplished with metaprogramming in Elixir… but probably a topic for a separate post.
Most Liked
codeanpeace
tl;dr in my experience, multiple function heads + guards > early returns
Coming from Ruby where I appreciated how early returns could un-nest code for readability, I remember searching for an equivalent when I began learning Elixir. My rule of thumb when using early returns in Ruby were to limit them to the beginning of a function body to avoid the need to “chase” down all the possible returns lurking within a function, which would reduce readability.
What I soon realized was that leveraging function arity aka multiple function heads and guards in Elixir accomplished much of what I wanted out of early returns in Ruby while pulling it out of the function body and into the function head – arguably improving readability. ¯\_(ツ)_/¯
dorgan
One thing about the particular snippet I posted is that the hypothetic check function wraps a conditional, to avoid ad hoc patterns like this:
with {:foo, true} <- {:foo, foo > 42},
more_steps_here do
profit!()
else,
{:foo, false} -> ...
end
so instead you’d do this:
with :ok <- check(foo > 42, :too_low),
more_steps_here do
profit!()
else
{:error, :too_low} -> ...
end
Your snippet assumes you already wrote functions that play nicely with with, while mine is more about the ad-hoc use cases, like checking for a bunch of predicates that are so mundane extracting them too functions would just add lots of verbosity and noise for the sake of using with
Related, I don’t particularly like having a specific tag like :foo_error instead of just :error with a more descriptive value because it makes it harder to write helpers that can just assume a normalized ok/error tuple. This is one thing I do find useful about ADTs like some people mentioned early, if you can assume normalized shapes, it’s easier to write composable functions.
benwilson512
I think this conversation is suffering from a lack of a definition about what counts as small or fast here. These are relative terms, and for people say, returning HTTP results, the overhead of a remote function call vs a local one is indeed small. For a hot loop processing a million records, maybe it isn’t small.







