> There's the common idiot of returning either `{:ok, ____}` or `{:error, ____}`, but what can be inside the error tuple is not always clear.
That's the result of Elixir being dynamic and not having built-in monads. You always have to check the docs/code.
> The other thing is that sometimes a function can both throw an exception and also return a success tuple.
I would say this is just poor design (though maybe someone could point me to an exemplary use of this?). As I see it (which is from how I've read it described and how most good lib do it):
- If function can error and the caller can do something about it, return :ok/:error.
- If the caller can't do anything about the error, raise.
- If the function can't fail, return a raw value.
It's true that it's a poor library implementation, but it's also a fairly natural consequence of a language with exceptions plus some other way of handling errors.
Even in a language like F#, just becauase a function returns an `option` type doesn't mean it won't also throw an exception sometimes. However, I don't necessarily think pure functional languages solve this either. If a Haskell function returns an option, you have little idea as to where the error originated. There are error types such as result types, which F# has as well, but then that's basically back to exceptions, perhaps even as a more limited form.
I'm curious if there's a language that's really nailed error handling. Erlang/Elixir have their supervisors and functional languages have pattern matching on option types, result types, and exceptions, but surely there's a way to improve on that.
That's the result of Elixir being dynamic and not having built-in monads. You always have to check the docs/code.
> The other thing is that sometimes a function can both throw an exception and also return a success tuple.
I would say this is just poor design (though maybe someone could point me to an exemplary use of this?). As I see it (which is from how I've read it described and how most good lib do it):