DEV Community

Discussion on: Language Features: Best and Worst

Collapse
 
_darrenburns profile image
Darren Burns

It has exceptions, but it's considered more idiomatic to return a tuple

Thread Thread
 
idanarye profile image
Idan Arye

OK, I see now. It's not Go's abomination like the word "tuple" implies, but more like the dynamically typed version of enum types.

At any rate, I find it a weird design choice to add exceptions and then encourage a different method of error handling. One of the main complaints about C++'s exception was that due to its legacy it had three types of error handling:

  1. Function returned value + errno.
  2. Setting a field on an object.
  3. Exceptions.

I guess Elixir wanted to go with pattern matching for error handling, but had to support exceptions for Erlang interop?

Thread Thread
 
isaacrstor profile image
Isaac Yonemoto • Edited

I guess Elixir wanted to go with pattern matching for error handling, but had to support exceptions for Erlang interop?

No. Elixir inherits from erlang's "let it fail" mentality. The PL itself supports supervision trees, restart semantics, etc. So in some cases, you want to just "stop what the thread is doing in its tracks and either throw it away or let the restart semantic kick in". In those cases, you raise an exception and monitor logs. The failure will be contained to the executing thread. You can then make a business decision as to whether or not you REALLY want to bother writing a handler for the error. Does it happen once in 10 thousand? 10 million? Once in a trillion? The scale and importance of the task will dictate whether or not you need to deal with it.

Other times when you might want to raise an exception 1) when you're scripting short tasks. Elixir lets you create "somewhat out of band tasks". Example: commands attached to your program that create/migrate/drop your databases.

In this case, most failures are 'total failures', and you don't care about the overall stability of the program, since it's "just an out-of-band task". So explicit full fledged error handling is more of a boilerplate burden.

2) when you're writing unit or integration tests. The test harness will catch errors anyways, so why bother with boilerplate. Use exceptions instead of error tuples.

Thread Thread
 
idanarye profile image
Idan Arye

Yes, exceptions makes sense. I agree with that. Using the returned value for error handling also makes sense - but only if you don't use exceptions. If the language supports exceptions, and not just aborts/panics - actual exceptions you are expected to catch because they indicate things that can reasonably happen and you need to handle - then you can't argue that using the returned value makes everything clear and deterministic and safe and surprises-free - because some function down the call-chain can potentially throw an exception.

So, if that function can potentially throw, you already need to code in exception-friendly way - specifically put all your cleanup code in RAII/finally/defer/whatever the language offers. And if you already have to do this for all cases - why not just use exceptions in all cases and not suffer from the confusion that is multiple error handling schemes?

Thread Thread
 
isaacrstor profile image
Isaac Yonemoto • Edited

You don't have to cleanup. That's the point. I can't explain it except to say, if you watch enough IT crowd, you might start to agree that sometimes it's okay to just "turn it off and back on again".

If your system is designed to tolerate thread aborts, it's really refreshing and liberating. Let's say I was making a life-critical application. In the event of a cosmic ray flipping a bit, I would much rather have a system that was architected where, say, of the less important subprocesses just panics and gets automatically restarted from a safe state, with the critical subprocesses still churning along, than a system that brings down everything because it expects to have everything exactly typechecked at runtime.

Thread Thread
 
idanarye profile image
Idan Arye

There is still cleanup going on. Something has to close the open file descriptors and network connections. You may not need to write the cleanup code yourself, as it happens behind the scenes, but as you have said - you need to design your code in a way that does not jam that automatic cleanup. For example, avoid a crashing subprocess from leaving a corrupted permanent state which the subprocess launched to replace it won't be able to handle.

One of the main arguments of the "exceptions are evil" movement is that having all these hidden control paths makes it hard to reason about the program's flow, especially cleanup code that needs to run in case of error. But... if you already need to design your program to account for the possibility of exceptions, you are losing the benefit of explicit flow control while paying the price of extra verbosity.

This convention in Elixir to prefer returning a tuple seems to me as more trendy than thoughtful...

Thread Thread
 
isaacrstor profile image
Isaac Yonemoto • Edited

You really don't have to worry about it. The VM takes care of it for you. Unlike go, there are process listeners that keep track of what's going on. File descriptors are owned by a process id, and if the id goes down it gets closed.

As a FP, most stuff us stateless and in order to use state you have to be very careful about it,so there usually isn't a whole lot of cleanup to do in general. As I said, I wrote some sloppy code in three days as a multinode networked testbench for an internal product and it was - it had to be - more stable than the code shipped by a senior dev (not in a BEAM language)

There is zero extra verbosity because you write zero lines of code to get these features.

As for the tuples, I wouldn't call it trendy since it's inherited from erlang, which has had it since the 80s.

I think you have been misinformed about elixir or erlang and suggest you give it a try before continuing to make assertions about it.

Thread Thread
 
idanarye profile image
Idan Arye

You really don't have to worry about it. The VM takes care of it for you. Unlike go, there are process listeners that keep track of what's going on. File descriptors are owned by a process id, and if the id goes down it gets closed.

Yup - higher level languages do that basic stuff for you. But it can't do all cleanup for you. For example, if a subprocess needs to write two files, and it crashed after writing the first file due to some exception, there will only be one file on the disk. You need to either account for the possibility there will only be one file (when you expected there to be zero or two) or do something to clean up that already-written file.

There is zero extra verbosity because you write zero lines of code to get these features.

I talked about verbosity in the no-exceptions style error handling, not the one in the exceptions style.

As for the tuples, I wouldn't call it trendy since it's inherited from erlang, which has had it since the 80s.

Erlang had tuples, but didn't use them for returned value based error handling. At least, not from what I could see with a quick google. Elixir does use them for error handling.

I think you have been misinformed about elixir or erlang and suggest you give it a try before continuing to make assertions about it.

My "assertions" about Elixir is that it uses both exceptions and pattern-matching-on-returned-values for error handling. Is this incorrect?

Thread Thread
 
isaacrstor profile image
Isaac Yonemoto • Edited

At least, not from what I could see with a quick google

ok tuples and error tuples are literally everywhere in erlang. The result type for gen_server start function, for example, is {ok, Pid} | ignore | {error, Error}.