Hacker News new | ask | show | jobs
by LessDmesg 2405 days ago
Even Haskell doesn't get it right: there are no exhaustiveness checks on pattern matches (something OCaml has), and while sum types provide closed unions, getting open unions is clunky (have to use typeclasses). OOP languages are imperfect in the opposite direction: open unions out of the box (inheritance) but no closed unions and no pattern matching. Seems language authors on the whole are bad thinking design spaces through. "The user might want any of three simple things X, Y, or Z, so let's choose the one that our language will support well, the one it will kind of support, and the one it won't support whatsoever just for the hell of it".
3 comments

> Even Haskell doesn't get it right: there are no exhaustiveness checks on pattern matches

Just enable -Wincomplete-patterns or -Wall!

It's just a warning, and it's opt-in. I would say that counts as not getting it right.
Haskell does have exhaustive checks. Its just a compiler flag which isn't default turned on (this kills me).
Can you please elaborate with some examples what you mean by "closed unions" and "open unions" here? I would appreciate a lot.
We say a sum type is "open" is more alternatives can be added later (or some alternatives can be removed later), perhaps in a different translation unit or a different library. We say it is "closed" if all alternatives must be specified at the definition, no alteration allowed.

For example if you write this in Haskell

    data PrimaryColor = Red | Green | Blue

    colorToInt Red = 1
    colorToInt Green = 2
    colorToInt Blue = 3
it is closed because future code can't add more alternatives. Instead you must simulate it using type classes, with a wildly different syntax, and (some would say) a hack. Or define a different sum type that wraps the above.

OCaml on the other hand supports the above perfectly with a slightly different syntax, but it also supports polymorphic variants (not to be confused with a Haskell sum type that's polymorphic because of a type variable like Maybe) which are lighter weight. For example you can write a function that takes different "constructors" without necessarily giving a definition for the type or listing all alternatives:

    let color_to_int = function
      | `Red -> 1 | `Green -> 2 | `Blue -> 3
Notice I never defined any type for the three colors! If you wish, you can mention the type:

    [< `Red | `Green | `Blue ]
or give it an alias to make it look more normal. But you can add (with some limitations) or remove more things to this variant later on.

To me, though, the most intuitive way of understanding this is to think of the first as nominal typing, whereas the second is structural typing.