Hacker News new | ask | show | jobs
by rbehrends 3219 days ago
A sum type `T = A | B` means that a value of type `T` can be either of type `A` or of type `B`. Such a type is used to express polymorphism.

A product type (from "Cartesian product", i.e tuples) `T = A * B` means that a value of type `T` has a component that is of type `A` and another component that is of type `B`. It is used to aggregate parts into a whole.

> Yes, I could go and research functional programming languages and type theory

It has absolutely nothing to do with functional programming and touches only upon the barest essentials of type theory (and calling it type theory is already stretching it, because it's just about defining a couple of common computer science concepts).

Sum and product types are fundamental computer science vocabulary to such an extent that it's not really possible to have a useful discussion about programming language semantics without them.

1 comments

>A sum type `T = A | B` means that a value of type `T` can be either of type `A` or of type `B`. Such a type is used to express polymorphism.

Taking this example (in English / pseudocode):

Define a class Animal.

Define Dog as a subclass of Animal.

Define Cat as a subclass of Animal.

Case 1) Now if we have a variable a1 that can, at runtime, contain (or refer to) either a Dog or an Animal instance.

Case 2) And if we have a variable a2 that can, at runtime, contain (or refer to) either a Dog or a Cat instance.

Referring to your quoted sentences above, would you say that Case 1, Case 2 or both, are about sum types?

Just trying to understand the terminology.

I'm thinking you just violated the Liskov substitution principle (https://en.wikipedia.org/wiki/Liskov_substitution_principle).

A sum type is the same thing as a tagged union, a variant record, or a discriminated union (https://en.wikipedia.org/wiki/Tagged_union).

I'll have to look at that, thanks.
I'm not sure if you can really express it in OO. Sum types are used to represent a closed set of variants. Inheritance like that isn't closed.

In languages that have both OO and ML/Haskell-style type systems like Scala, the fact that the set is closed is denoted by the keyword 'sealed'.

example

sealed trait Color

final case object Red extends Color

final case object Green extends Color

Color can only be Red OR Green. In your example you can have an Animal, a Dog, a Cat, and other things can inherit Animal and create more variants.

>Color can only be Red OR Green. In your example you can have an Animal, a Dog, a Cat, and other things can inherit Animal and create more variants.

True. I used traditional OO style inheritance in my example, and my question was about understanding the relationship between it and sum types, based on rbehrends' comment that I was replying to. Interesting, did not know this. So you are saying that sum types are sort of like inheritance with some limits - pre-defined types only. Sort of like an enum (though those do not have inheritance in them) but for classes or types.

> So you are saying that sum types are sort of like inheritance with some limits

Traditionally sum types don't have inheritance, you have the sum type is a type and the variants (or constructors) are values of that type, but yes you could also model it as a "closed inheritance" hierarchy, in fact some languages do exactly that: Scala with sealed traits and case classes (the trait is the sum type, each class is a variant) and Kotlin with sealed classes.

The boundaries can also get fuzzy the other way around, OCaml has polymorphic variants and extensible variants which blur the line. Polymorphic variants are structural types where multiple sum types share the same value (polymorphic variant types are venn diagrams essentially), not entirely unlike integral types, and extensible variants (as their name denotes) can get cases added to them by users of the base package/type.

> Sort of like an enum

Sum types can be seen as an extension of enums yes, Rust and Swift call them that, in both languages they're enums where each "value" (variant) can carry different data sets (unlike e.g. Java enums where variants are instances of the enum class). Here's a "degenerate" C-style enum with Rust:

    enum Color { Blue, Red, Green }
and here's one with associated data:

    enum Color {
        RGB(u8, u8, u8),
        HSL(Hue, Saturation, Lightness),
        CMYK(u8, u8, u8, u8),
    }
Good info, thanks.
>final case object Red extends Color

On a side note, that syntax seems a little counter-intuitive - the extends word seems to indicate that Red is a subclass of Color, but from what you said seems more like Red and Green are values for Color. So it seems like an enum would be more appropriate here (for this use of Red, Green and Color)?

A Scala “object” declaration declares a singleton; it's an object, but you can also define object-specific class features.

If the instances need behavior (which in practice they often do), this is useful. Presumably, in the example, they would in a substantive program, but the behavior is irrelevant to the illustration and thus omitted.

Interesting, thanks.
Both. Assuming that you can actually exclude instances of class Cat in case 1 and of Animal in case 2, that is.

In case 1, the actual type of a1 would be Animal | Dog. In case 2, the actual type of a2 would be Dog | Cat. (Either a1 or a2 might be declared with a different type, this is about the values that they can actually hold, according to your stated premises.)

>Both. Assuming that you can actually exclude instances of class Cat in case 1 and of Animal in case 2, that is.

Good point. I was actually thinking in terms of Python OOP, and in that, you cannot exclude those instances (at least not without some extra code).

Animal is a sum type. It's definition is:

    Animal = Cat | Dog
I personally didn't like the comparison, because inheritance is more powerful (what is not always good), and because algebraic types takes most of their usefulness by merging sums and products on the same type.
What languages are you familiar with? It might help with explaining the concepts.

Since we're on a Go thread, I'll point out that Go's structs and tuples are both examples of product types.

Most familiar with Python currently. Done some Ruby and Java and C and Pascal earlier. Some D and a bit of C++ and a bit of Go. I do understand that structs and tuples are examples of product types (because the range of the values for a struct or tuple is the Cartesian product of all the possible values for each field). My question was mainly about sum types as described by rbehrends, was trying to relate them in my mind (somewhat, if it makes sense) to traditional inheritance as in Python, Java or C++.
Cool, so unions in C and C++ are a kind of sum type because the variable can have one of a set of types. More usually people think of tagged unions when they think of sum types, I believe Pascal's Variant Records are an example of tagged unions.
Thanks. Yes, I was thinking on the same lines. Don't remember whether Pascal also has this right now (more time since I used it than C), but in C, being the somewhat more flexible language that it is, IIRC you can also store a value of one type (out of the types of the sum type) in the union, and then read it back out as one of the other types. E.g. define a union with an int (say 16-bit) and a two-char (or two-byte) array, and then write into it as an int, and read it back out as two chars or two bytes. There are uses for such things, though I can't think of a good example of the top of my head. Okay, one use might be hardware interfacing, another might be conversion between big-endian and little-endian data values if you need to roll your own for some reason, I know there are libs for that).