3 ms·
> I don't think typed errors + isinstance checks are really the alternative. It's the alternative proposed by the article, because the code from the article is
by dahfizz 4y ago
> I don't think typed errors + isinstance checks are really the alternative.
It's the alternative proposed by the article, because the code from the article is 5 years old. The author's claim is that calling isinstance is better than exceptions.
Pattern matching makes error objects nicer, but still doesn't make them compelling for the general case.
There are cases, like HTTP requests, where it makes sense to embed status in the response object. But, critically, every http response has both a status and some content. Every response object is the same type and can be treated in the same way.
The article's proposal is to return either a string, or one of a handful of error objects.
I will grant that some APIs are nicer when you embed status information in the response type. That is a special case. Preferring error objects over exceptions in the general case is wrong, IMO.
- derefr 4y agoI think things become a lot clearer when you unpack which APIs those "some APIs" are. The case where sum-typed return types (because that's what we're really talking about here) truly shine, isn't in handling "errors" in the sense of invalidated preconditions that you need the caller to handle. Rather, the case where sum-typed return types make sense, is in handling condition states more generally. When I say "state" here, think "finite-state machine." Or actually, maybe think "pushdown automata." Imagine a sum type representing the state of a door, like: Open | Closed(door_tag: DoorTag() | None) | ClosedAndLocked(required_key_bidding) If you call door.try_open(), it makes perfect sense to return this sum type. This type represents the new state of the door, after your attempt. None of the types are exceptional per se, because this is just an attempt to open the door. You're equally happy to have the door in any state afterward. "Open if you can", per se. Certainly, you could write another function door.open() † that does throw an exception if the door cannot be opened. It might also raise a DoorAlreadyOpenException. But this function makes more sense to implement on top of the first function, than the other way around. Door.try_open is more core to how the door models its state and reacts to input, than Door.open is. † In other languages, the naming-complexity expectation would be reversed — the non-raising variant would be `Door.open`, while the raising variant would be something like `Door.open!`.