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

I may be wrong, but I don't think this is what people mean by 'sum types'.

I was expecting union types (e.g., https://www.typescriptlang.org/docs/handbook/unions-and-inte... or https://dotty.epfl.ch/docs/reference/new-types/union-types.h... )

I guess enums are a type of sum type, but seems this is more specifically about enums



Simple way to think about it is plus. Sum plus sum increases, union plus union does nothing.

1. A union type is like int | double. It means the value is one of a set of possible types. And it's a true set: `int | int | double` is indistinguishable from `int | double`.

2. A sum type is a new type built from a list of other types, which assigns a 'tag' to each possible list element, like a Rust enum. The tags are not themselves types: they are like a struct field name. It's quite possible to have a sum type with N tags, each wrapping `int`.

So with this understanding Rust has sum types but not union types, while TypeScript has union types but not sum types.


It is possible (and popular) to have sum types in TypeScript by manually adding a tag, though.

  type Maybe<T> =
    | { kind: "Some", value: T }
    | { kind: "None" }


This is also why sum types are often referred to as tagged unions. They are a special case of a union, in Rust's case with support from the type system.


Yep, Typescript's control flow analysis, type literals, and union types allow us have tagged unions/sum types. Probably its best feature.


I've always considered both the union and tagged union to be Sum types[0].

> Two common classes of algebraic types are product types (i.e., tuples and records) and sum types (i.e., tagged or disjoint unions, coproduct types or variant types).

Edit: e.g. X | Y isn't so different from 'a' A | 'b' B

[0] https://en.wikipedia.org/wiki/Algebraic_data_type


> X | Y isn't so different from 'a' A | 'b' B

Except for the fact that the union of a type with itself (X | X) doesn't add any extra values, while a tagged union can have distinct tags with the same data type ('a' A | 'b' A) and actually sums the possible values for each tag. They are equivalent only so long as the unions are disjoint with no overlap between the members.


I think we're saying the same thing. I was saying it's like thinking of 'a' A as an X and 'b' B as a Y.


TypeScript does have tagged unions in addition to untagged ones https://www.typescriptlang.org/docs/handbook/release-notes/t...


Rust also has 'union' types [1] that work similar to the union types in C, C++.

Recently there has also been a lot of discussion [2] about possibly adding anonymous enums to the language, and whether these should have sum or union semantics.

[1]https://doc.rust-lang.org/reference/items/unions.html

[2]https://internals.rust-lang.org/t/ideas-around-anonymous-enu...


WRT to 2, one useful property is that you can have two or more labels for the anonymous type `()`, such as `enum MyBool { True, False }` or `enum JSON { List(Array<JSON>), Object(List<(String, JSON)>), Number(f64), String(String), True, False, Null }`.


Thanks! This clarifies the situation


A sum type is a tagged union [1]. In C, a sum type can be modeled by the combination of an enum and a union. The enum would tell you which field of the union you should use. In Rust, enum's subsume unions and enums of C. This is analogous to a a disjoint union [2] in set theory. Notice that in both cases you have a pair of data. Sum types and disjoint unions are both instances of coproducts in category theory, by the way.

1. https://en.wikipedia.org/wiki/Tagged_union

2. https://en.wikipedia.org/wiki/Disjoint_union


According to the tagged union Wikipedia you referenced, a disjoint union is also considered a tagged union. Whether or not that's actually a correct description within the vernacular of computer science, I don't know.

"In computer science, a tagged union, also called a variant, variant record, choice type, discriminated union, disjoint union, sum type or coproduct, ..."


They're all what you call isomorphic to eachother, which means roughly any adequate mathematical model of one can mapped to any other.


I believe another word for enums (in the Rust sense, not in the C++ or Java sense) are tagged unions.


That's right. It's unfortunate that Rust calls them "enums", since as you noted that term already had a well-established meaning in other languages, and the concept that Rust calls "enums" also had several existing names (sum type, coproduct type, disjoint union, and, more generally, algebraic data type) in the literature and in prior languages.

Language designers: stop changing the meanings of words! I know you mean well and you're trying to tie unfamiliar ideas to familiar ones for beginners, but you end up causing more confusion for everyone in the long run.


Rust enums are also enums:

  enum Colour {
    Red,
    Blue,
    Green,
  }
is fine.


When you say "Rust enums are also enums", that's not true in general. What is true is that _some_ Rust "enums" (like your `Colour` example) can be represented as traditional enums. You don't prove a universal quantification with a single example.


All enums are expressible as Rust enums.


More than that:

    #[derive(Debug)]
    enum Colour {
        Red = 1,
        Blue = 2,
        Green = 4,
    }

    fn main() {
        println!("{:?}", Colour::Blue);
        println!("{}", Colour::Blue as u8);
    }


> It's unfortunate that Rust calls them "enums"

Rust calls them enum to be familiar to people coming from C-family languages, which is a large target.

> since as you noted that term already had a well-established meaning in other languages

Rust enums "degenerate" to a C-style enum (except typesafe) as it can be repr'd to a number and it's possible to select the discriminant (if there's no associated data).

> the concept that Rust calls "enums" also had several existing names (sum type, coproduct type, disjoint union, and, more generally, algebraic data type) in the literature and in prior languages.

Pretty much none of which are actually part of the language e.g. in Haskell or OCaml the designator is `type`, and it's used for both sum types and product types: the sum type simply has a single constructor.

But Rust doesn't use `type`, it uses `struct`. And a `struct` with multiple variants doesn't make sense.


Can't edit but

> the sum type simply has a single constructor.

this should be "product" not "sum"


Enum has one meaning in C family languages. Other languages may have different meanings. For example, in Haskell something is an enum if there is an invertible mapping between that type and (a contiguous subset of) the machine integers (with complications).

In other languages the concept might correspond to (finite) recursively enumerable sets (ie you can list all their elements).


> I may be wrong, but I don't think this is what people mean by 'sum types'.

It is.

> I was expecting union types (e.g., https://www.typescriptlang.org/docs/handbook/unions-and-inte.... or https://dotty.epfl.ch/docs/reference/new-types/union-types.h.... )

Union types are basically an anonymous form of sum types. In the same way tuples and structs / records are both forms of product types.

Statically typed languages which support sum types generally only support the named version (because it tends to be more useful and powerful).

> I guess enums are a type of sum type, but seems this is more specifically about enums

Depends on the language, C's enums are not types at all, Java's or C++'s are product types. Technically enums (or more generally tagged unions, which is what Rust's enums are) is a superset of sum types as you can have multiple variants of the same type, but that's not leveraged here.


> Union types are basically an anonymous form of sum types.

This is false on multiple levels. First, being a sum type has nothing to do with the type being named vs. anonymous. What makes a sum type a sum type is that it's a categorical coproduct, whether you give it a name or not. Second, sum types are synonymous with _disjoint_ (i.e., tagged) unions, not unions. Consider the union of boolean with itself. The result would be equivalent to boolean, because union is an idempotent operation. Disjoint union, or sum, would give you a type with 4 values instead of 2.

> Technically enums (or more generally tagged unions, which is what Rust's enums are) is a superset of sum types as you can have multiple variants of the same type

That just how sum types work (have you ever wondered why they are called "sum" in the first place?). You're thinking of union types.


Union types aren't sum types. One difference is A | A = A, but A + A = 2 * A.


Rust enums are full union types. i.e. while each possibility in a (say) Java enum must be of the same type, each possibility in a Rust enum can be of a different type.

i.e.

https://play.rust-lang.org/?version=stable&mode=debug&editio...


However, Rust's enum-type members are still named - to my knowledge you can't have anonymous enum type members in Rust.

In the example you gave, the type-of `left` is still `SumType::Left` instead of being just `String`.

I'm not too familiar with Rust to say, but I don't consider this to be a syntactically zero-cost abstraction (even if the wrapper-types are elided by the compiler) because we still have more keyboard typing to do than we should be doing, imo.


Ah, that's to enable you to have multiple members that could hold `String` values. Yeah, it comes with the (syntactic) cost that you have to identify the member.


In practice that's really not a problem because of rust's great pattern matching that easily lets you extract even deeply nested values.


I think the use of an enum (in the Rust example) makes this a sum type situation: https://tonyarcieri.com/a-quick-tour-of-rusts-type-system-pa...


This is just a difference between named and anonymous types, really. This is very similar to named functions vs lambda functions, both provide the same exact functionality but avoiding superfluous identifiers makes programming more ergonomic.

More specifically sum types in Rust must also be tagged, meaning you need to explicitly construct and deconstruct them. This aspect is along the type-alias vs newtype axis which gets into structural vs nominative typing and the trade-offs therein.

So yes, Rust enums and Typescript union types are both exactly sum-types and the differences are due to the surrounding decisions made about the languages. Rust's sum types are named and tagged, Typescript's are anonymous and untagged, but they're both sum types.


> So yes, Rust enums and Typescript union types are both exactly sum-types…

As others have pointed out, untagged unions are not sum types because the union of a type with itself has no effect, whereas adding a type to itself yields twice as many possible values. Untagged unions can function as sum types when there is no overlap between the members, but not in the general case.

To illustrate the difference: You can construct every possible algebraic type as some combination of void (no values), unit (one value), sum (|A + B| = |A| + |B|), and product (|A * B| = |A| * |B|). This does not work if the sum type is replaced with an untagged union. You can't even get as far as constructing the equivalent of the boolean type; while |Unit + Unit| has two distinct values, |Unit ⋃ Unit| only has one value.


Even the word "enum" is quite overloaded; i.e. compare Swift's robust class-like enums with those of C or C++.


I guess technically what people want us algebraic types. Sums of products. Just sums alone are just enums.




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

Search: