News for hackers. Stuff that matters.
Re:Railway oriented programming by Animats on Thursday June 23, 2016 @01:39AM (#11959066)
The gyrations needed for error handling in functional programming are painful. As Rust libraries become more functional, Rust is getting more of those problems.

The problems created by not having exceptions are tougher than the problems created by having exceptions.

Re:Railway oriented programming by wldlyinaccurate on Thursday June 23, 2016 @03:25AM (#11959399)
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?

Re:Railway oriented programming by cousin_it on Thursday June 23, 2016 @04:03AM (#11959475)
Compared 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.

Re:Railway oriented programming by gpderetta on Thursday June 23, 2016 @05:26AM (#11959748)
4) 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.

Re:Railway oriented programming by pacala on Thursday June 23, 2016 @09:42AM (#11960749)
Nothing 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.
Re:Railway oriented programming by gpderetta on Thursday June 23, 2016 @11:48AM (#11961908)
It 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.
Re:Railway oriented programming by larozin on Thursday June 23, 2016 @05:40AM (#11959795)
1) 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.

Re:Railway oriented programming by insulanian on Thursday June 23, 2016 @07:29AM (#11960192)
> 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.

Re:Railway oriented programming by nerdponx on Thursday June 23, 2016 @08:05AM (#11960305)
What 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.

Re:Railway oriented programming by piaste on Thursday June 23, 2016 @08:44AM (#11960452)
You 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.

Re:Railway oriented programming by nerdponx on Monday July 04, 2016 @10:10AM (#12030859)
Believe it or not, I was just working on a script this weekend that was becoming a nightmare to debug. And when I realized that I had been lazily using exceptions as control flow, this really hit home.
Re:Railway oriented programming by cousin_it on Thursday June 23, 2016 @07:39AM (#11960215)
Just wait until the same people get hold of monads to implement their business logic.
Re:Railway oriented programming by exDM69 on Thursday June 23, 2016 @05:34AM (#11959766)
IO 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.

Re:Railway oriented programming by fpoling on Thursday June 23, 2016 @08:53AM (#11960492)
3) 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.

Re:Railway oriented programming by Rexxar on Thursday June 23, 2016 @09:54AM (#11960822)
C++ exception can be "zero cost" in the non exceptional path (for example http://llvm.org/docs/ExceptionHandling.html#itanium-abi-zero...)
Re:Railway oriented programming by fpoling on Thursday June 23, 2016 @10:29AM (#11961105)
Presence of exceptions still prevents some optimizations that the compiler can do otherwise like omitting frame pointer etc.
Re:Railway oriented programming by tigershark on Thursday June 23, 2016 @04:44AM (#11959623)
Did 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.
Re:Railway oriented programming by cousin_it on Thursday June 23, 2016 @04:58AM (#11959673)
I'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.

Re:Railway oriented programming by gclaramunt on Thursday June 23, 2016 @10:06AM (#11960911)
For 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.

Re:Railway oriented programming by techdragon on Thursday June 23, 2016 @01:50AM (#11959104)
Erlang style "fail noisily and let the supervisor handle it" is quite a solid way to deal with "exception handling bloat" in any paradigm, functional or otherwise... The hard part is finding a way to create effective layers of supervision.
Re:Railway oriented programming by hvidgaard on Thursday June 23, 2016 @02:45AM (#11959271)
An exception is a noisy failure, and the supervisor is whatever try/catch that it bubles up to. Still, Erlang style has it's own complexities. It is in essence a network of micro services, and creating that overlay network and manuvering in it isn't simple.
Re:Railway oriented programming by aangjie on Thursday June 23, 2016 @04:53AM (#11959658)
Yep.. with python assert statements + supervisorctl has been a very good setup for scaling a low latency, clean chat filtering app.
Re:Railway oriented programming by hvidgaard on Thursday June 23, 2016 @02:43AM (#11959261)
F# has exceptions. It's a tool that has it use at times, but they are in essence a goto
Re:Railway oriented programming by brian_cloutier on Thursday June 23, 2016 @03:47AM (#11959440)
Every single control flow tool you've ever used is in essence a goto. Every conditional, every loop, every function call. Many modern function calls are in some respects _worse_ than gotos ever were, you don't even know which code you're going to jump to.

It's not goto's that were so painful, it was unstructured gotos. Calling exceptions gotos is not a reasonable criticism. They have some very well-defined structure, and additional benefits like separating your happy-path code from your error-handling code, making both simpler.

Re:Railway oriented programming by mafribe on Thursday June 23, 2016 @05:36AM (#11959774)
It's important that there are two very different approaches to control flow manipulation. They differ in when the target of a jump is bound.

- Static binding of location to jump to. This is what goto, callcc, while, for etc. do.

- Dynamic binding of location to jump to. This is what exceptions do.

You can show that they are really different, in the sense that (in the absence of powerful language features), they cannot simulate each other, see [1]. Once you realise that exceptions and gotos are quite different thinking about error handling becomes more clearer.

[1] J. G. Riecke, H. Thielecke, Typed Exceptions and Continuations Cannot Macro-Express Each Other.

Re:Railway oriented programming by hvidgaard on Thursday June 23, 2016 @06:52AM (#11960053)
I accidently hit downvote on your post, and I cannot undo it. I hope some kind soul will upvote you to counter. Your post has solid information for the topic at hand.
Re:Railway oriented programming by brian_cloutier on Thursday June 23, 2016 @07:09AM (#11960120)
That's a really cool result, thanks for pointing it out!
Re:Railway oriented programming by hvidgaard on Thursday June 23, 2016 @06:48AM (#11960026)
The comparison to goto was not meant as criticism, but because both disrupt the control flow in a way that has no structure. Conditionals, loops, and calls do have structure, they are the flow.

When a language support contructions such that normal control flow event are either "success + result", or "failure + error", it's much easier to follow, for me, and experience has taught me that it is far less error prone. Exceptions have a use for things that are unexpected, but happened anyway, such as OutOfMemoryException.

Re:Railway oriented programming by codingmyway on Thursday June 23, 2016 @10:02AM (#11960885)
In essence exceptions are an implicit variant (discriminated union) extension of the return type of a function.

So a function returning T can actually return T or one of the set of Exception types.

Re:Railway oriented programming by piaste on Thursday June 23, 2016 @10:26AM (#11961089)
No, I think there's an essential difference here.

The return type of a function is always handled by its direct caller and only by its direct caller.

Exceptions are fundamentally different because they aren't necessarily handled by the caller, but by the nearest appropriate exception handler, wherever it is, regardless of scope.

That is both their strength when they are used appropriately, and their weakness when they are not.

Re:Railway oriented programming by codingmyway on Thursday June 23, 2016 @10:52AM (#11961322)
You're right in that the implicit bit extends all the way up the call stack. They aren't the same in practice, just in theory.

That implicit parallel 'track' is useful in doing the Foo => Result<Foo, Error> for you in the form of Result<Foo, Exception> but bad in not warning of unmatched values and hiding the fact. Like many a tool it can be abused by those who know just enough to use it but not enough to know why.

This is why Java originally forced you to make thrown exceptions explicit. This railway approach is one good way of dealing with error conditions explicitly.

Re:Railway oriented programming by codingmyway on Thursday June 23, 2016 @10:27AM (#11961097)
Exceptions as discriminated unions in C# http://tomasp.net/blog/2015/csharp-pattern-matching/
Re:Railway oriented programming by cel1ne on Thursday June 23, 2016 @07:22AM (#11960167)
Re:Railway oriented programming by cbd1984 on Thursday June 23, 2016 @03:10AM (#11959352)
Haskell has exceptions and monads. Either can be used for error handling; monads are, admittedly, unusual, but the error-handing type monads (as opposed to side-effect-handling type monads) are easy to understand in terms of wrapping values (or the potential for a lack of value) such that the type system can be used to both enforce a certain level of care and enable a certain insensitivity to errors.

For example, the Maybe monad is the simplest way to handle a function which might not return a usable value: A value wrapped in a Maybe monad has a type of either (Just a) (with a being any type at all) or Nothing. Here's a trivial example, straight from the ghci REPL:

    > let mdiv x y = if y == 0 then Nothing else (Just (x / y))
    > let foo x = (Just (x + 5))
    > let bar y = (Just (y / 2))
    > mdiv 5 6 >>= foo >== bar
    Just 2.9166666666666665
    > mdiv 5 0 >>= foo >>= bar
    Nothing
See? Complete insensitivity to errors until the last possible moment. Much less noisy than the C equivalent would be, although in this specific example I suppose the Inf semantics of the IEEE floating point standard would be equivalent.