5 ms·
I personally find this "railway" approach (which is essentially a glorified Either) much less painful than dealing with exceptions. Did you really find the exa
by wldlyinaccurate 10y ago
I personally find this "railway" approach (which is essentially a glorified Either) much less painful than dealing with exceptions.
Did you really find the example imperative error handling code more elegant than the railway-style function composition?
- cousin_it 10y agoCompared to exceptions, the Either approach to error handling has some big problems: 1) Good luck annotating all potential cases of divide by zero or IO error, and all callers of those. 2) Everyone must pay the runtime cost of wrapping and unwrapping on every operation, even if nothing fails. 3) The type of a function that can fail in ways A and B isn't the same as the type of a function that can fail in ways B and A. 4) Good luck getting a stack trace. I think unchecked exceptions are better for most applications, where errors are rare and have many different types.
- tigershark 10y agoDid you even read the article? For the points 1 and 4 F# has exceptions that are handled seamlessly with this technique using a simple function in the chain, as clearly showed in the article. For point 2 you are just using a different operator from the normal composition one. I don't understand your point 3.
- cousin_it 10y agoI've read the article and don't completely understand your comment. For point 1, if you want to use the railway approach instead of exceptions, you must annotate all functions in the chain. For point 2, I was talking about the runtime cost. For point 3, Result<Result<Foo,A>,B> isn't the same type as Result<Result<Foo,B>,A>. People in the Haskell world acknowledge that it's a big problem ("monads don't compose"), hence all the research on monad transformers and extensible effects etc. For point 4, if your function is the lowest in the call stack and decides to report an error using the railway approach, you don't get a stack trace.
- gclaramunt 10y agoFor 1 and 4, is not for runtime exceptions (div by 0, out of memory, etc) that you need to bubble up to the top, is for "logic"/checked exceptions where you want to use code to deal with the failure case. For 3, that totally defeats the purpose, the whole point is to have a single error type (it can capture the details of the error anyway) then you can have Foo -> Result<Foo1,Error> Foo1 -> Result<Foo2,Error> Foo2 -> Result<Foo3,Error> And use bind to compose them easily. I think the confusing part is the word "exception" as it mixes concepts.
- gpderetta 10y ago4) Good luck getting a stack trace. That's a great point. At the end of the day, Either and (checked) exceptions are equivalent from a semantic point of view, but having exceptions being an explicit part of the language means much better support for tooling and debugging.
- pacala 10y agoNothing prevents the system to add a stack trace on the Error branch of an Either. It's just that some evaluation strategies [lazy] that are usually associated with functionally programming [Haskell] provide unusual reduction traces. An eagerly evaluated expression would add the usual stack traces.
- gpderetta 10y agoIt is not just stack traces. If an exception is not handled, the debugger will break at the frame that trowing the exception (at leat on sane implementations that do two phase unwind), while with either (and error codes) all state useful for debugging will have been destroyed.
- exDM69 10y agoIO errors, division by zero and other truly exceptional situations are well suited for exceptions. Validation cases like the examples on the article are not exceptional and the failure cases must be handled properly. In this case it makes sense to use an Either -like solution where the compiler will warn if there are unhandled cases. Both styles of error handling are useful in some cases and both have their issues.
- deleted 10y ago[deleted]
- larozin 10y ago1) This is kind of invariant failures (logic errors that should never happens) and handled by panics in the same way as C++ exceptions. 2) In most cases Result<T, E>/Optional is returned from a heavyweight operation (I/O, dictionary lookup, etc) compared to an easily predicted error checking branch. 3) Probably this is problems of a type system or design, not an error handling strategy. 4) Good luck of getting a stack trace of re-thrown (propogated) exception in C++. Exceptions are not too bad, but they are very often misused.
- insulanian 10y ago> Exceptions are not too bad, but they are very often misused. This! The worst is when people start using them to control the flow of business logic. I hate that.
- cousin_it 10y agoJust wait until the same people get hold of monads to implement their business logic.
- nerdponx 10y agoWhat actually is so bad about doing this? I'm a data scientist, not the software engineer, so forgive me if this is something you all learned in CS 101.
- piaste 10y agoYou know how GOTO is real bad? Well, using exceptions for flow control* is basically a thinly disguised GOTO. Also, constructing an exception object, with the full stacktrace etc., is a pretty heavy operation and can cause big performance issues. But it's mostly the first thing. * Which is an important distinction. Flow control exception are recognisable because you intentionally throw an exception and expect it to be caught by a particular catcher to process it in a particular way - much like a GOTO, and with all its fragility. In the proper usage of exceptions, when you throw an exception you don't care which line of code catches it and when you catch an exception you don't care which line of code threw it.
- fpoling 10y ago3) and 4) are essentially subclasses of 1) and to solve 1) one needs a typesystem like in Koka or PureScript where code is polymorphic regarding effects so introducing new one without breaking existing code is possible. PureScript uses that to precisely model JS exceptions. As for 2) the exceptions are not zero-costs even with modern implementations and profile-driven optimizing compiler makes the runtime cost of wrapping/unwrapping low.
- Rexxar 10y agoC++ exception can be "zero cost" in the non exceptional path (for example http://llvm.org/docs/ExceptionHandling.html#itanium-abi-zero-cost-exception-handling http://llvm.org/docs/ExceptionHandling.html#itanium-abi-zero...)
- fpoling 10y agoPresence of exceptions still prevents some optimizations that the compiler can do otherwise like omitting frame pointer etc.