Personally, I think this leads to hard to reason about code due to the loss of locality between the function body and the else that may or may not be triggered. It’s the same reason goto is “considered harmful” (caveats and exceptions to that aside
)
This feels like a place where with would be a good solution. A bit of syntatic sugar around it to tease the behavior of with to automatically succeed on {:ok, anything} and fail on {:error, anything} would be nicer imho, and iirc someone recently did exactly that.
Also, I find that if I have long(er) functions where there are conditions somewhere in the middle that need to branch, such as on failure or success, that this is a signal that perhaps I should break that function there and send processing on to another function which will continue on success or return on failure.
IOW, each function goes as far as it can until need to react to a success/failure and at that point calls another function. So each function ends up being a bit-sized bit of code that does execute completely, allowing each function to be reasoned about simply and clearly.
Exceptions (or, what they really are: early returns) are a tool of “last resort” for me, used when they are really required only. Similarly for case/with/etc. If those start nesting, or a function ends up being a series of them, I take that as a code smell to start chopping that function up into more atomic sets of code to stuff into functions.


















