cjbottaro
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.
Trending in Discussions
Other Trending Topics
Chat & Discussions>Discussions
Latest on Elixir Forum
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










Showing Posts 1 to 10- Show Best Posts
- Show All Posts (oldest first)
- Show All Posts (newest first)
D4no0
Early returns are a remnant of goto statements from languages like cobol, they encourage break of flow. A classical example would be:
Now you can notice here that while such a function would work without problems if early returns were a thing in elixir, it is introducing a notion that we can return data from everywhere, hence breaking the concept of what a map function should be able to do, introducing a new concept that you can break out of all contexts.
This example can be as well rewritten to:
What is interesting is that there are cases where you would want to return early, a good example would be
Enum.reduce_while/3and it is easily possible to implement this without using throw/catch.cjbottaro
I guess it’s a testament to how deep I am into functional programming these days that I never even considered returning from an enumeration, ha. That’s a really good point.
How do you feel about it in the case of replacing a
withstatement, or generally unravelling heavily nested cast/if/cond?D4no0
I think that this is easily solvable by refactoring to small functions and match parameters instead of using case/cond.
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. ¯\_(ツ)_/¯
cjbottaro
That’s what I’ve been doing for the past many years and I guess why I’m struggling with this now. I prefer the early returns in some, maybe a lot of, cases now that I’ve just given in and started using them again.
I guess it comes to down to style. I find it massively difficult to trace through code that is broken up into tons of small functions. The constant jumping between files/modules/functions adds a lot of mental overhead compared to just reading a larger function top to bottom.
Side note, our Ruby code base adhered to the small methods, single responsibility object mantras and it’s one of the worst codebases to work in ever, imo. Literally everyone hates its. And it’s all because seemingly simple things (like generating an Elasticsearch query, executing it, massaging the results) are spread across dozens of files and classes.
I’m starting to feel that a tiny bit in our Elixir codebase, I think due to a lot of reasons discussed here about
with: https://forum.elixirforum.com/t/with-statement-else-index/56914 and I kinda feel like early returns helps in some cases.sodapopcan
Ya, this was a big problem with a lot of Rubyists. At an old job there were literally single use private functions that had the same body as the function name just without underscores. Stuff like:
Agreed that this was a nightmare to work on.
Of course it all comes down to style, but I feel like there is a good middle ground. I’ve been writing larger functions since moving to Elixir and haven’t ever wished for early returns. Not like my code is particularly amazing or anything but I often strive for a large “primary” function that literally lays out everything (especially side effects) then have a few smaller functions for implementations. Though they are “smaller” in the Ruby sense—they could 10-20 lines each themselves depending on what they do.
The super small functions are only good in my estimation if they are very reusable everywhere and you get to know them. But single line private functions are generally the worst.
D4no0
My general opinion on early return is that this is a optimization feature and in no way increases readability or correctness of the code, it is an unsafe feature that is used for code optimization in low-level languages. A simple example:
This can be easily refactored to:
We can notice that we have to allocate 2 variables and write more code, essentially the primitives and the programming style does not offer support to write code in a way where you would be encouraged to not use a break or return early.
I write now a lot of Kotlin and the functions that are always the most unreadable and hardest to debug are functions that return early, especially since you can early return there from anywhere, be it callbacks, loops, you name it.
D4no0
My 5 cents on one of the potential problems on having small functions and having a hard time to navigate to them is over-engineering.
I tend to write those small functions always in the same module, usually near the function as private functions, while others tend to extract them to a separate module, thinking that they will be able to use them in the future in other places, witch they will most definetly not use and if they do they can write the same function again there. I think that this optimization of code duplication is the main reason of such problems, and this problem is even bigger if we talk about typed code, classes, people there end up with some abomination inheritance trees and generic classes that you have to look at for 10 minutes to understand what it actually does.
al2o3cr
FWIW, one of the few uses of
throwin the Elixir stdlib is inKeyword.keysinside a:lists.mapcall - specifically for stacktrace-hygiene reasons:https://github.com/elixir-lang/elixir/pull/10214#issuecomment-661473002
Regarding OP’s code, I’m not a fan - in a function called by a function that said
returnable, thereturnfunction will do a nonlocal return (!) which is unlikely to match the expectations of anybody whether they like early returns or not. IMOtry/catchandthrowaren’t complicated enough to be worth hiding behind a thin abstraction layer.cjbottaro
Our Ruby code base should be a case study on exactly this. Inheritance 5 levels deep, but then also composed of multiple other objects with similar inheritance levels. It’s absolute insanity.
I also find a lot of the times that inheritance is abused just to get rid of explicit branching logic (i.e. people don’t like using
ifstatements for some reason).Can you explain what you mean by this?