Hacker News new | ask | show | jobs
by lalaithion 1779 days ago
I think this article makes the "sum" and "product" terms out to be very complicated, when in fact it's quite simple. If we have two enums

    enum Bool {
        True,
        False
    }

    enum Status {
        Waiting,
        Successful,
        Failed
    }
We can combine them into a product type, like a tuple, or a sum type, like a union.

    type Product = (Bool, Status)
    type Sum = Bool | Status
Now, we ask ourselves, what are the valid values of type Product?

    (True, Waiting)
    (True, Successful)
    (True, Failed)
    (False, Waiting)
    (False, Successful)
    (False, Failed)
There are two Bool values, and three Status values, so there are 2×3=6 values for the product of Bool and Status. Now, what about the Sum type?

    True
    False
    Waiting
    Successful
    Failed
We have 2+3=5 total values for the sum of Bool and Product.

Now, obviously, this math doesn't quite work out for types with infinite values, like strings or bigints or arrays, but that's where the analogy comes from.

If you extend this further, you can even figure out what an exponential type would be: the exponential type Bool^Status is a function that takes a Status as its argument and returns a Bool.

5 comments

The Sum is called Union in the TypeScript docs, as it narrows the valid values to the overlapping elements.

In the switch statement it becomes a Sum value (as shown in the article).

In ReScript (shown as well, which uses OCaml under the hood) the syntax is that of a Variant, in which each element is a different nominal type.

Good point. For example,

    type NotAProduct = Bool | Bool
only has 2 values, not 4. For a real product type, we need

    type Product = {tag: "Left", value: Bool} | {tag: "Right", value: Bool}
which has the requisite 4 values.
This is also called a "discriminated union", with "tag" being the discriminator here.
So to replicate a Rust enum, C tagged union, or C++ std::variant containing one byte for the type and the rest of the object inline, JavaScript needs a boxed object wrapping a string and the actual object contained inside? And the engine can't optimize away the boxed object into an inline value type because it could have multiple aliasing references in multiple locations? Boxing-based languages make me sad.
First I've heard of exponential type... to make sure I get it, you're saying there are 2^3 possible functions which take a status and return a bool? So if the enum inputs are success, wait, fail, you'd have

Fff

Tff

Ftf

Fft

Ttf

Tft

Ftt

Ttt

All as possible implementations of that function. Neat...

> I think this article makes the "sum" and "product" terms out to be very complicated, (...)

Indeed it would drive the point home if instead of simply mentioning product they referred to Cartesian product.

I guess sum types imply adding together two domain, but unless there's a subtlety then union would be clearer as well.

This is a great explanation for its length, thanks!
This helps handle IO in pure functional languages because? Is it because functions can have more than 1 type?
Can you elaborate what you mean by that? Support of sum and product types are used everywhere in typed functional languages and I don't know of any way they are exploited specifically in IO in a way that does not generalize to other domains.
Whoops looks like I confused algebraic datatypes and algebraic effects. Actually this confusion / realization because of your comment just clicked something for me, so thanks!!