Hacker News new | ask | show | jobs
by madeofpalk 767 days ago
Also Typescript https://www.typescriptlang.org/docs/handbook/2/everyday-type...
2 comments

Union types are not the same as sum types.
TS narrows union types cases based on conditionals like "if" (called discriminated unions in the docs in the past), and supports exhaustiveness checks. How do they differ in functionality from sum types?
Sum types are disjoint unions. This `T` has three cases

     L = { tag: "a", payload: string } | { tag: "b", payload: number }
     R = { tag: "b", payload: number } | { tag: "c", payload: boolean }
     T = L | R  
whereas a proper sum type `L + R` would have four.
Isn't that a completely useless distinction?

For all purposes and intents, the "b" type in L and R should be treated the same, no? What do you gain by not doing that??

This often comes up when writing a function which returns a wrapper over a generic type (like Option<T>). If your Option type is T | null, then there's no way to distinguish between a null returned by the function or a null that is part of T.

As a concrete example, consider a map with a method get(key: K) -> Option<V>. How do you tell the difference between a missing key and a key which contains `null` as a value?

This is trivial to model by making your type `T | null | Missing`.
`T | null` is equivalent to T. You can assign null to `T`>

It's like saying `string | "foo"` it is simply `string` due to subtyping.

No, it isn't "completely useless".

If you have a function that will normally return a string, but can sometimes fail due to reasons, you may wish to yield an error message in the latter case. So you're going to be returning a string, or a string.

It's not what the content of the data is; it's how you're supposed to interpret it. You have two cases, success and failure, and control will flow differently depending on that, not based strictly on the type of data at hand. We just model those cases in a type.

Why would you return `string | string` here? Wouldn't you explicitly mark the error, and return `string | error`? (substitute error with whatever type you want there - null, Error, or your own thing)
No disrespect, but that still sounds entirely useless to me. I would never model something as `String | String` as that makes zero sense. You should use a `Result` or `Either` type for that like everyone does.
As you've demonstrated, you can always construct sum types in typescript with the use of explicit discriminants:

    T = {tag: "L", payload: L} | {tag: "R", payload: R}
The real issue is typescript doesn't have pattern-matching, which make operating on these sum types inelegant
Supports exhaustiveness checks only if you explicitly opt-in it (by coding to pattern where you use helper function that accepts `never` type). "Dicriminated Unions Type"/"Sum Types" feels very hacky there, at least syntax-wise, because it is constraint by being "JS + types" language. It's remarkable what Typescript can do, but having native Discriminated Unions in JS (hence in TS too) would be much more ergonomic and powerful.
F# too. And Elm. But I get your point.