3 ms·
I don't, and I say this as a long-time user of C++'s std::variant and boost::variant, which are effectively union types. Foo | Bar makes sense when Foo and Bar
by amluto 1y ago
I don't, and I say this as a long-time user of C++'s std::variant and boost::variant, which are effectively union types.
Foo | Bar makes sense when Foo and Bar are logically similar and their primary difference is the difference in type. This is actually rather rare. One example would be a term in your favorite configuration markup language along the lines of JSON or YAML:
type Term = String | List<String>;
or perhaps a fancier recursive one:
type Term = String | Box<List<Term>>;
or however you want to spell it. Here a Term is something in the language, and there are two kinds of terms: string or lists.
But most of the time that I've wanted a sum type, I have a set of logical things that my type can represent, and each of those things has an associated type of the data they carry. Result types (success or error) are absolutely in that category. And doing this wrong can result in a mess. For example, if instead of Result, you have SuccessVal | Error, then the only way to distinguish success from error is to literally, or parametrically in a generic, spell out SuccessVal or Error. And there are nasty pathological cases, for example, what if you want a function that parses a string into an Error? You would want to write:
fn parse_error(input_string: &str) -> Error | Error
Whoops!
- jffaufwwasd 1y agoThis is where you should be combining them. Ok' and Err' as nominal type constructors which are unioned: struct Ok<T>(T); struct Err<E>(E); fn parse_error(input_string: &str) -> Ok Error | Err (ErrorA | ErrorB | ErrorC...) Or make a sum type enum Result<T, E> { Ok(T), Err(E), } fn parse_error(input_string: &str) -> Result<Error, (ErrorA | ErrorB | ErrorC...)> The error types are unioned for easy composition but you have a top level sum type to differentiate between success and failure.
- anon-3988 1y agoThis is brilliant, I feel like Rust should ultimately arrive at this but imagine this is very hard to implement from the tooling perspective.
- amluto 1y agoAh, so you want both sum and union types, and you probably want those unions to be genuinely unordered, so that A | B is the same as B | A. And maybe even with inferred subtype relationships or automatic conversions so that A | B can be used where A | B | C is expected. This could be useful. I can also imagine it resulting in horrible compilation times and/or generated code bloat in a language+toolchain like Rust that insists on monomorphizing everything.