Hacker News new | ask | show | jobs
by Jeff_Brown 2405 days ago
When I found sum types (in Haskell) I could not believe they had not been part of any prior language I'd learned. You can kind of fake them with inheritance but it's awkward, and you don't get totality checking, and you can't close the set of alternatives, so a new descendent added later can break things.
5 comments

> I could not believe they had not been part of any prior language I'd learned.

Huh? They're part of Pascal (known as variant records) and plenty of other languages besides. It's mostly C that lacks them, and even then you're just expected to implement them yourself, for maximum efficiency.

> Huh? They're part of Pascal (known as variant records) and plenty of other languages besides. It's mostly C that lacks them, and even then you're just expected to implement them yourself, for maximum efficiency.

"plenty of other languages besides" doesn't matter if those languages aren't being used in practice.

Let me list off the top 10 languages (incl markup) from the most recent Stack Overflow survey, specifically the Professional Developers tab. https://insights.stackoverflow.com/survey/2019#technology-_-...

* JavaScript

* HTML/CSS

* SQL

* Python

* Java

* Bash/Shell/PowerShell

* C#

* PHP

* TypeScript

* C++

You can argue about the demographics of Stack Overflow or the bias in users who actually respond on these surveys or a million other things. But based on job postings I've seen in the last several years, I'd wager this list is pretty accurate.

Yeah. You're not wrong. Sadly, while HN is full of people of great technical expertise, it's also full of people whose knee-jerk reaction to reading _anything_ here is to think of how they can say it's wrong, preferably in a way that implies or explicitly states that they're bigger experts than the person they're replying to. I find it very off-putting, personally, but I keep wading through the crap because (thankfully) that's not the whole story.
Swift is being used in practice and will most likely see more use in the future as Objective-C fades.
If you follow the link you'll find Swift is already above Objective-C on that survey, just that neither of them made the top ten.
I did follow the link, but I disagree with your conclusion that being used by at least 6.6% of respondents to make apps for hundereds of millions of users amounts to "not being used in practice".
If you take my comment in isolation, that's a reasonable read.

But it was a response to the following comment:

> > I could not believe they had not been part of any prior language I'd learned.

> Huh? They're part of Pascal (known as variant records) and plenty of other languages besides. It's mostly C that lacks them, and even then you're just expected to implement them yourself, for maximum efficiency.

With the context of that post, I hope you would understand that I am disagreeing with the insinuation that every programmer has used "Pascal [...] and plenty of other languages besides". You can easily learn five different languages and finally end up in Haskell without having encountered ADTs before.

They're not present in the dominant statically typed languages (C/C++/C#/Java/Go) and not really a thing in dynamic languages generally, and so the vast majority of programmers never encounter the concept.
Well, Pascal has been dominant for a short time before C and its family really took off. But that was such a long time ago that I kind of forgot about it. C (without ++) was nice because you could write code without too much boilerplate compare to Pascal and it was noticeably faster (computers were slow.) However any mistake with pointers could crash the computer if it was a DOS PC and not a UNIX workstation.
C++ has std::variant, but that's pretty new.
My understanding is that's not really a sum type, though -- e.g., you couldn't use it to add a type to itself, only to add distinct types to each other.
No, you can add a type to itself (that is a pain in the ass though). You might be thinking of the fact that it may not hold a value (eg. if an exception is thrown while moving into it), but they made it as close to a sum as they could given the nature of C++.
It's been a long time since I did anything with Pascal, but if I recall variant records correctly, they're not very different from C's unions. In particular, I don't recall variant records having anything like totality checking. I don't even recall it having anything that could tell you what kind of thing it currently holds. But as I said, it's been a long time...
> They're part of Pascal (known as variant records) and plenty of other languages besides.

It sounds like GP is saying they had never used of those languages beforehand. I'm not sure why would be so surprising given that it doesn't accompany any other information like "and I had used 20 other languages before it".

C has unions, which are similar to Pascal’s variant records, though without an explicit out of band discriminant.
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".
> 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.

They're included in Rust. But Rust of course feels very much like a strict-by-default Haskell in some ways.

(Now with optional laziness.)

i had no idea rust supported laziness! how does that work?
I think he refers to async recently landing in Rust.

Here the Rust book mentions it:

> async bodies and other futures are lazy: they do nothing until they are run.

https://rust-lang.github.io/async-book/03_async_await/01_cha...

I just read it, and thought it was odd. I don't understand how Rust's async relates to a laziness as in Haskell's laziness.

Iterators are also lazy.

All of these things build up some form of computation that doesn’t execute until later. Until then, they’re represented by some kind of data structure.

I feel lucky that I learned Standard ML among the early languages I learned as a teenager, which got me into this mindset. That was way back before the monadic revolution.
C has Union, while not exactly the same as adt, the essential idea is the same.
A C union is a sum, but not much of a type, since it's never checked (not even at runtime like Python) and using it wrong in your code will silently corrupt your program.