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

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


Yeah, that's the bad definition that leads to problems like the one the OP describes.


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`?

Also


data A = A

data B = B

data X = XA A | XB B

data Y = YA A | YB B

f :: X -> () f = undefined

let r = f (undefined :: Y)

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.




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

Search: