Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Yes, but in a functional paradigm.


This just isn't true at all. Exceptions use a totally different mechanism from railway-oriented programming, have very different constraints, and different performance characteristics.

Please don't entertain these silly posts. At best, it only grants legitimacy to a crassly presented idea.


I'm wondering: why not make every returned value implicitly of type Either with Left some generic type?

In most cases, I'm not interested in the types of exception that can be thrown. And when I need the type of an exception, a runtime check suffices in almost all cases.

Let's not make exception handling more cumbersome than it needs to be, and only invoke verbose mechanisms when necessary.


> I'm wondering: why not make every returned value implicitly of type Either with Left some generic type?

Because then you lose the ability to have code that doesn't error. If you explicitly mark those parts of your code that can error, you can then start to decouple those parts that can error from those that will never error. E.g. you can separate the business logic which figures out which database query you need to run (which can't error) from the code that actually performs database queries (which contains timeouts, retry logic etc.), and then you can test those two things separately and make sure you've covered all the cases. Whereas with pervasive/implicit exceptions, every line of your business logic might throw an exception (or might not throw an exception now, but later be refactored to throw one), so you need to test a combinatorial number of cases (or, more likely, your tests won't cover some code paths).


Wrote a reply to a deleted comment, figured it was worth keeping to expand on my point:

> - what if the tables / schema / etc doesn't exist?

Then figuring out which query to run won't fail, executing the query will fail; the whole point was to separate the two.

> - what if the logic which selects the DB query returns no query?

You make that impossible, if that's not a valid answer. (If that is a valid answer then you use a return type that represents that - Maybe/Option - and then you're forced to handle that porperly). Most programming languages don't even permit a function to "return no value" so that's already not a problem. (Some programming languages permit a function to return "null" or loop forever; don't use those languages).

> - what if the logic attempts to pull a value from an out of bounds array (or similar fail)? Sure you would check the bounds before querying the array but why did you get to a situation where you tried to pull an out of bounds index in the first place?

So you don't get into that situation: you only use array indices that are valid by construction, you don't give yourself any way to construct an invalid index. (Ensuring that indices into a specific array are a different type from indices into any other array is a useful first step. Of course, in practice you probably avoid using arrays and indices at all; if you use higher-level operations like map and reduce then there aren't any indices to worry about).

(Very occasionally there may be cases where you can't avoid having to do something that might error, in which case what you want to do is fail-fast, erlang style. But if you're doing what I'm advocating, this will be your only option: the point of all the above is that we make invalid states unrepresentable, so when we get into an invalid state it's simply impossible to continue. And really the vast majority of the time - every time actually, in my experience - you can find a way to write the code so that it can't error, if you actually try).


Realistically, almost all but the simplest code can produce an error, starting from out-of-memory errors.

You can break up code according to many criteria, but doing it based on whether the code throws an error or not seems not very practical, since you can break up code only once.

Further you could have smart test tools that can figure out what exceptions can be thrown in what circumstances. You don't need to bother the programmer with them.


> Realistically, almost all but the simplest code can produce an error, starting from out-of-memory errors.

If you care about handling out-of-memory errors then you absolutely do want to distinguish between code that allocates and code that does not. The vast majority of application code is content to treat out-of-memory as an impossible condition and fail-fast in the case where it does happen. So sure, technically the distinction is between code that can produce errors that you want to handle and code that cannot produce errors that you want to handle, but the point goes through all the same.

> You can break up code according to many criteria, but doing it based on whether the code throws an error or not seems not very practical, since you can break up code only once.

Nonsense. You can, and should, decompose your code along multiple axes, including any effects, of which error handling is indeed only one.

> Further you could have smart test tools that can figure out what exceptions can be thrown in what circumstances. You don't need to bother the programmer with them.

"smart test tools" would amount to an ad-hoc, informally specified, bug-ridden implementation of half a type system. The distinction between code that can error and code that cannot should be as lightweight as possible, but it mustn't be completely elided, because the programmer needs to be able to see it when they're working on the code. The "=" versus "<-" distinction in Haskell-style "do notation" is the best compromise I've seen: it's minimally intrusive, but it is visible.


Not all functions can produce an error. If you add two arbitrary precision integers, you're not getting an error.


So (+) would be defined as adding its operands, except if one of them is an error (in which case the result would be an error).


Either is a sum type. What if I don't want that. It's not about handling errors per se, but modeling the type.


Yes, I understand. But by "implicitly" I really meant that. You don't see the type, only the compiler does. The programmer only sees the non-error part of the type (the "Right" part of Either).


How could this work? I specify an interface to an api.

C Foo(A a) = ...

Someone then provides me with some not-A error value. What should I do? What do you mean by implicit.


> Either is a sum type. What if I don't want that.

Product types work as well, if you want. But unless your return type is a monoid (or like, a semilattice maybe?) then you're going to end up with Sum Types somewhere.

The vast, vast majority of software engineers and students work with sum types every day and they're totally fine with them. Most languages implicitly sum many values with null.


Unless you're running in a multi-threaded environment or run out of memory?


These sounds like exceptions rather than errors to me. Difference being, exceptions truly are exceptional and not anticipated; like running out of memory. How do you even handle that? Would probably let it crash and restart (Erlang model). Errors is something you anticipate can happen, such as getting 4xx-5xx back from the server and know what to do in those cases.

Then it's fair to say that you definitely can't get an error by adding two numbers.


> How do you even handle that?

You request a smaller block of memory? Not everyone is always requesting the minimum they need right now.

> Would probably let it crash and restart (Erlang model)

The Erlang model is to let a partial failure trigger a less complex part of the system to resume that logic based on a strategy.

> Then it's fair to say that you definitely can't get an error by adding two numbers.

I mean, you can also accept garbage back, I guess?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: