4 ms·
While I broadly agree with this, the author doesn't appear to go far enough himself. > Functional error handling, using Option and Result types, is rapidly bec
by cubefox 3y ago
While I broadly agree with this, the author doesn't appear to go far enough himself.
> Functional error handling, using Option and Result types, is rapidly becoming the standard operating procedure in essentially every language, because it relies on nothing but values and types. They are more of the same, and so they fit right into the existing language machinery.
I agree that Optional types are better than checked exceptions, but they are worse than union types. A function with the union return type String|Error (read: String or Error) is allowed to return another function with return type String. Similarly, a function which accepts the type String|Error as a parameter also accepts parameters of type String. With Option types this doesn't work. Option<String> and String are incompatible types, so code has to be rewritten if the types change.
Personally, I would go so far as to say that union types should replace exceptions in general, checked or unchecked, as well as any implicit nullabiliy, which can be replaced with the explicit union type Foo|Null.
(Although some special syntax for handling Foo|Exception or Foo|Null is probably a good idea, as "error" and "nothing" are pretty general categories.)
- Blikkentrekker 3y agoThe problem with union types for exceptions is that it becomes impossible to return an error as the correct case, or at least there is no way to discriminate any more. Functions that deal with errors also need to be able to have exceptional situations, and in many cases there are completely generic functions as well that can error out for generic reasons. A basic example is the elementary case of having a list of errors for some reason and indexing out of bounds producing an error, what if one have a list of values, which are all derived from itself accessing lists, each of which contains “Ok:Val+Err:OutOfBounds”, accessing that list itself now becomes indeterminate. The other issue is that the language needs to have support for union types rather than merely sum types. And there's a good reason almost no statically typed language has support for union types since it removes provability and it's not something that can in easily be statically decided, requiring dynamic type tags, but then again, that's what sum types effectively are as well. Typed Racket is statically typed and has union types, but it's an extra layer on top of a dynamically typed language, so the type tags where there already, but it's not easy to erase type data at run time not to implement. In your example of “String|Error”, it essentially requires that every String is now tagged at runtime with tag information about it in practice.
- cubefox 3y ago> And there's a good reason almost no statically typed language has support for union types For what it's worth, I learned about union types from Ceylon, which was statically typed.
- Blikkentrekker 3y agoWhich is a very obscure language. Type erasure at runtime simply isn't really possible with full union typing and looking at it as a consequence Ceylon has to box everything into a structure which contains a type tag which is obviously not an overhead a language such as Rust is willing to pay.
- Skeime 3y agoI often run into the opposite problem: There are union types, but I would really like sum types (i.e. tagged unions). The common case is a data structure where I cannot guarantee that the user will not want to use Null as a value in my data structure, but I also need to represent the absence of values myself. With a sum type, my absence would be None and the user’s absence would be Some(None). With union types, I need to somehow create an extra sentinel value myself.
- cubefox 3y agoSomething like String|NoAnswer|None doesn't sound too bad to me here...
- Skeime 3y agoExcept that NoAnswer has none of the language’s (and it’s library’s) support for the built-in None. And you might run into problems when trying nest the data structure, which might be a reasonable thing for your users to do.
- cubefox 3y agoI just think that the nesting offered by Option & Co has overall much more downsides than upsides compared to union types.
- DougBTX 3y agoThis isn’t just a problem with custom types, it affects generic iteration types too. With a next() -> Option<T> method, an Iterator can iterate over any type of element.
- cubefox 3y agoYeah, but there could be a type T for "any type" additional to union types, basically like TRUE in Boolean logic. (Theoretically you could also have F for "no type", intersection types (AND), negation types (not Foo), and any logical complex of those.)
- Sinidir 3y agoYeah Containers like Option<T> and Result<T> not having a proper subtyping relation to T is major flaw. I mean you are basically giving a stronger guarantee if you are returning T instead of T or an Error, yet you break every callsite. I have been thinking about this in terms of a data oriented programming language like clojure, but having types/schemas. If you use Union Types for something like getting a value out of a map for a specific key, then if you knew the schema of a specific map you could reduce the return type from {T, Error} to just the type of the value T that you know is there. Basically a sufficiently smart compiler with the necessary information could make you not have to deal with errors at all in certain cases. With Result/Option/Maybe this would not be possible. It would always infect the entire callstack and you would always have to deal with it.
- Kwpolska 3y agoIf your language supports implicit user-defined conversions (like C#), you could `return "foo";` from a method returning `Result<string>`.