5 ms·
Is this fundamentally different than returning the data or throwing an exception to be caught elsewhere?
by rco8786 3y ago
Is this fundamentally different than returning the data or throwing an exception to be caught elsewhere?
- golergka 3y agoType system doesn't force you to handle exceptions, they're spooky action at a distance.
- naasking 3y agoSome do, like checked exceptions.
- Karellen 3y agoAlternatively, they're just reducing boilerplate. Yes, anything might return an error. Do we need to really write `or error` or whatever everywhere that all we're going to do is pass the error immediately back up the stack? One way to improve this would be to have some kind of way of declaring the atypical case where you have a function that can never throw an exception... which is a feature of some exception-based programming languages.
- rco8786 3y ago> Do we need to really write `or error` or whatever everywhere that all we're going to do is pass the error immediately back up the stack? Right, exactly. Like in Go: if (err != nil) return err You find this littered all over every Go codebase. And somehow people seem to not realize that they’re doing the exact same thing as throwing an exception (unwinding the stack until something handles the error), just manually and painfully.
- kamray23 3y agoIt can be important and reasonable. You might have things which never give errors. You might have things which sometimes do, and you attempt to use them in places which you don't believe to give errors. Here, the great thing about error functors is that the type system comes out and warns you very, very explicitly. Hell, we add that same boilerplate back in a lot of languages with things like "noexcept" and "throws", with the compiler's type checker doing the exact same thing and forcing error handling in a "noexcept" when it calls something which throws. You get an isomorphic system, it works the exact same way, it's effectively an error functor as far as the type checking is concerned. That being said, this article sells the whole concept of functors incredibly short. You use them to represent certain concepts, such as in this example where the concept being represented is "value, but not everywhere in the domain" or "value or a different type of value". On top of that, these are applicative functors, meaning that they also represent a way of combining two values in a specific manner, and they are monads, meaning that they represent a kind of composition. That means that these same concepts of railway tracks suddenly allow for a lot larger set of tools: you can represent as a functor the concept of having multiple values, you can consider lists to be applicative functors with the combination of two lists being an operation such as concatenation, zipping of sequences, or even cartesian products. You can even go as far as to consider lists monads, in which case their compositional behaviour could be one of non-determinism: each function takes in a single value and evaluates to multiple values – the composition would be running the function over every value in the list (over all possible states), then concatenating the lists that come out (creating a new list of possible states). Treating errors and non-determinism as special cases of a similar behaviour allows for extending many of the methods we use in error handling to this kind of application as well. Not that these two are the only uses either. It's a much larger toolbox, though going overboard with it will result in unreadable hellish code.
- chriswarbo 3y agoIndeed, exception handlers are essentially the `COME FROM` madness from INTERCAL ;)
- mpweiher 3y agoDefine "fundamentally". In the end it all comes down to NAND gates... ;-) But yes, it is very, very different. With an exception, which does achieve a similar effect of stopping further execution right there and then, you need to throw the exception and you need to check it. With dataflow, all you have to do is nothing. And exceptions are quite a tricky and heavyweight mechanism. What about the intermediate code? Does it need to run despite the exception? Will it just swallow the exception instead of passing it on? You can just pass the final error handler in and set it on the filter and there is no trickiness. And of course without the exception, there is a lot of code that needs to do error handling despite not really being involved. This becomes really, really noticeable when your async code is handled using callbacks. You just get two sets of callback everywhere. Just having the code be synchronous would be better, but with dataflow the whole issue just evaporated.
- gpderetta 3y agoIn the end it boils down to continuations: your function either either takes a single return continuation that takes result|error variant or it takes two (or more) continuations: one for the happy path, and one for the error path. The former maps well to result objects, the latter is maps better to non local (checked) exceptions, but it easy to see how to transform from one to the other.
- mpweiher 3y ago> In the end it boils down to continuations No it does not. > your function Only if you deal exclusively with procedures/functions. With dataflow, that's not the case. You have two filters, and if the first filter detects an error, it just does nothing (i.e it does not pass the value to the second filter). Done. No need for exceptions or continuations, or weird return types.