Oh, here comes my usual rant about "algebraic" sum types that break all of the algebra rules on sums...
On those languages, if you define the types X = A + B and Y = A + B, and if you try to match X = Y, they don't match at all. Besides, they don't compose (X = A + B, Y = X + C, Y = A + B + C is false).
The result is that if you go and declare `read :: Handler -> Either FileAccessError String`, you can't just declare your function as `f :: String -> (FileAccessError | NetworkError | MyCustomError)` expect it to work well with the rest of your code. Because every time you need a different set here, you'll have to declare a different type, and transform the data from one type to the other.
The "sum" and "product" is more about the cardinality of the type. If you have a type A with 3 possible values and B with 5 possible values, then the sum type X = A+B has 8 possible distinct values while the product type Y = A*B has 15 possible values
You would want union types for that, they behave more like what you have in mind. (Scala 3 has such a feature, as an example)
Also, Rich Hickey's Maybe Not talk is sort of about this "non-composability" of ADTs. E.g. a function accepting a String can be relaxed to accept `String | None` without recompile, but changing it to `Maybe<String>` would make a refactor necessary.
At the same time Maybe<Maybe<String>> may encode a useful state that will be unrepresentable with `(String | None) | None = String | None`.
Yes, unions are the proper type sums. Just as fixed cardinality sets are the proper type products, not tuples. I didn't know about this feature in Scala 3, all it seems to be missing for a fully algebraic sum is type negation (like `FileError and not FileSeekUnsupportedError`).
> At the same time Maybe<Maybe<String>> may encode a useful state that will be unrepresentable with `(String | None) | None = String | None`.
We already have the operation that takes a type and converts it into a non-matching type. It doesn't have a well accepted name, in Haskell it's called `newtype` but every language with strict types has it.
There is no need to mix those operations in the fundamental types in a language. The usual emuns and tuples are just less expressive suggared synonyms.
I don't understand how they don't compose, from your example:
(X = A + B, Y = X + C, Y = A + B + C is false)
I understand that types aren't math values, but isn't the point of using a `+` to describe the communicative value of the type so that `(A + B) + C = A + B + C`?
There's absolutely no way to write this so it compiles. In fact, there isn't even a way to define the composed types so that they only express a sum, you have to add extra baggage.
On those languages, if you define the types X = A + B and Y = A + B, and if you try to match X = Y, they don't match at all. Besides, they don't compose (X = A + B, Y = X + C, Y = A + B + C is false).
The result is that if you go and declare `read :: Handler -> Either FileAccessError String`, you can't just declare your function as `f :: String -> (FileAccessError | NetworkError | MyCustomError)` expect it to work well with the rest of your code. Because every time you need a different set here, you'll have to declare a different type, and transform the data from one type to the other.