<p>If you have two product types <code>A×B×C</code> and <code>D×E×F</code> you can construct a new type <code>A×B×C×D×E×F</code>. In typescript (structural typing) this is called intersection <code>&amp;</code>. Should this operation be called something different in nominally typed languages? (since the intersection of different nominal types is empty)</p>
<p>If you have two sum types <code>A+B+C</code> and <code>D+E+F</code> you can construct a new type <code>A+B+C+D+E+F</code>. In typescript (structural typing) this is called union <code>|</code>.</p>
<p>Do any languages have built in support for these operations? (aside for golang (product types only) and typescript)</p>
<p>In C you can always do</p>
<pre class="lang-cpp prettyprint-override"><code>struct A;
struct B;
struct C { A a; B b; };
</code></pre>
<p>In rust you can do</p>
<pre class="lang-rust prettyprint-override"><code>enum EnumA {
    VariantA1,
    VariantA2,
}
enum EnumB {
    VariantB1,
    VariantB2,
}
enum CombinedEnum {
    A(EnumA),
    B(EnumB),
}
</code></pre>
<p>However, this doesn't take advantage of the weak associativity of product and sum types i.e. (A×B)×(C×D)=A×B×C×D, but you get stuck working with (A×B)×(C×D). What languages allow you to take advantage of this associativity? What should this be called? For example Golang allows you to do struct embedding</p>
<pre class="lang-go prettyprint-override"><code>type TypeA struct {
    PropertyA string
}
type TypeB struct {
    PropertyB int
}
type CombinedType struct {
    TypeA
    TypeB
}
</code></pre>
<p>It was pointed out that typescript's <code>&amp;</code> does not really a product operator, neither is typescript's <code>|</code> really a sum operator.</p>

<algebraic-data-types>
Intersection and unions for product and sum types
CC BY-SA 4.0
2024-06-24T02:43:06.520Z
Is there a reason to expect them to be called something different in a nominal environment? Union types are union types. When you say "type", do you mean something constructible (e.g. a Java class), or only a descriptor of values (e.g. a Java interface)?
Michael Homer
CC BY-SA 4.0
2024-06-24T02:47:42.597Z
In c it would make sense that a union of union types produce a union. But an intersection of product types produce a product type. It always strikes everyone as unintuitive. Maybe product types should be called intersection types
Tom Huntington
CC BY-SA 4.0
2024-06-24T03:37:13.417Z
You might enjoy reading *Types and Programming Languages* - there are several sections that walk through different implementations of products and sums, from both a nominal viewpoint and a structural viewpoint.
apropos
CC BY-SA 4.0
2024-06-24T15:44:58.440Z
Notice that TypeScript's intersection types are not product types at all. Compare `A×A` to `A&A`
Bergi
CC BY-SA 4.0
2024-06-24T17:17:09.223Z
Python's static typing system supports Union types (in modern language spelled as `A | B`) and looks for Intersection types for last [8 years](https://github.com/python/typing/issues/213).
SUTerliakov
CC BY-SA 4.0
2024-06-24T18:05:21.753Z
Typescript's intersection types (`&`) are not product types, and its union types (`|`) are not sum types. Notably, `T & T` and `T | T` are equal to `T`, but these are not generally true of product or sum types. It's also not generally true that an intersection of nominal types is empty, e.g. `Serializable & Cloneable` in Java.
kaya3
CC BY-SA 4.0
2024-06-24T18:22:01.973Z
It's worth noting that mathematically, the Cartesian product `×` is not literally associative according to its set-theoretic definition, but there is an easy bijection between the products `A×(B×C)` and `(A×B)×C`, so nobody ever really cares about the formal difference, and we write `A×B×C` and tuples like `(a, b, c)` because everybody agrees it is a better notation than always writing `(a, (b, c))`. Likewise, the disjoint union of sets is not literally associative based on its definition, but mathematically it's often convenient to abuse notation and pretend it is.
kaya3
CC BY-SA 4.0
2024-06-24T21:44:11.117Z
@kaya3 yes, ChatGPT said it was called: associativity up to isomorphism or weak associativity
Tom Huntington
CC BY-SA 4.0
2024-06-24T21:48:27.357Z
Was it ChatGPT that said Typescript's `&` is a product type, or `|` is a sum type? Do yourself a favour, and don't ask ChatGPT this kind of thing.
kaya3
<p>For product types, to make the operation associative and commutative, I don't know if there are other ways, but the obvious solution is to make every field having a natural name.</p>
<p>Using the field name as the natural name, is probably not a good design in modern statically typed languages. In dynamically typed languages, it seems inferior to just use no types at all, and check whether the field names exist in situations that need to check the type.</p>
<p>It makes more sense to use a combination of the field name and the type as the natural name, where the later one could be omitted if no ambiguity is found. We could see it's just inheritance. (Inheritance marked as <code>virtual</code> in C++, and interfaces in C# and Java, would make the operation also idempotent.) It's just not easy to use the product as an actual type without some advanced templating. But you could add every type as a constraint in templated code where a product type is expected.</p>
<p>For sum types, there is usually a &quot;top type&quot; that is the sum of all types, or at least all types with a vtable. The vtable pointer works better than enums for this purpose, as the sequential order is not predetermined. Sum types of exact types are better implemented as subsets of the top type, by adding constraints. But again, it is usually not easy to use as an actual type.</p>
<p>The reason they are not directly supported as types, might be because of types usually describe exact memory arrangements, but the sum and product types are abstract concepts that ignored such details. But they came back with other more abstract concepts in programming languages that are just not called types.</p>

CC BY-SA 4.0
2024-06-24T11:45:41.920Z
2024-06-24T11:45:41.920Z
user23013
CC BY-SA 4.0
2024-06-24T21:34:34.997Z
You wouldn't make a product commutative would you? It would be natural just to preserve the order. I cant see memory arrangements being the reason why rust does not do enum embedding similar to golang's struct embedding
Tom Huntington
<p>This is <em>somewhat</em> possible in OOP languages that have both <a href="https://kotlinlang.org/docs/sealed-classes.html" rel="nofollow noreferrer">sealed interfaces</a> and <a href="https://docs.oracle.com/en/java/javase/17/language/records.html" rel="nofollow noreferrer">&quot;records&quot;</a> (AKA <a href="https://kotlinlang.org/docs/data-classes.html" rel="nofollow noreferrer">&quot;data classes&quot;</a>, <a href="https://docs.scala-lang.org/tour/case-classes.html" rel="nofollow noreferrer">&quot;case classes&quot;</a>), such as <a href="https://openjdk.org/projects/jdk/21/" rel="nofollow noreferrer">Java 21</a>, <a href="https://www.scala-lang.org/" rel="nofollow noreferrer">Scala</a>, and <a href="https://kotlinlang.org/" rel="nofollow noreferrer">Kotlin</a>. The caveat is that you must own all of the types, i.e. they can't be in a dependency or standard library, or you will have to make wrappers to put them in the union or intersection.</p>
<p>For example, let's say you have the following &quot;algebraic data types&quot; (example is Kotlin, but Scala and Java can do the same with different syntax):</p>
<pre class="lang-kotlin prettyprint-override"><code>sealed interface Currency {
    data class US(val dollars: Int, val cents: Int) : Currency
    data class CAD(val dollars: Int, val cents: Int) : Currency
    data class BTC(val bitcoins: Float) : Currency
}

sealed interface Length {
    data class US(val miles: Int, val feet: Int, val inches: Int, val centimeters: Float) : Length
    data class Euro(val meters: Float) : Length
}
</code></pre>
<p>To define a union, define a sealed interface that is inherited by all members, like so:</p>
<pre class="lang-kotlin prettyprint-override"><code>sealed interface Quantity {}

sealed class Currency : Quantity { ... }

sealed class Length : Quantity { ... }
</code></pre>
<p>You can still do everything with <code>Currency</code> and <code>Length</code> that you could if they weren't part of the union. But now you can also define variables of type <code>Quantity</code> that can be inhabited by both currencies and lengths. And importantly, like a true &quot;union type&quot; and unlike a &quot;sum type&quot; (your Rust example), they don't have to be manually wrapped in <code>Quantity.Currency</code> and <code>Quantity.Length</code>. You can even pattern-match on the union, and then further pattern match on the &quot;sum type&quot;s.</p>
<pre class="lang-kotlin prettyprint-override"><code>when (quantity) {
  is Currency -&gt; when (val currency = quantity) {
    is Currency.US -&gt; ...
    is Currency.CAD -&gt; ...
    is Currency.BTC -&gt; ...
  }
  is Length -&gt; when (val length = quantity) {
    is Length.US -&gt; ...
    is Length.Euro -&gt; ...
  }
}
</code></pre>
<p>Defining an &quot;intersection&quot; type is more complicated, and doesn't preserve every functionality of the product types; specifically, you'll no longer be able to de-structure them, although you can access their fields by name. To define an intersection, split all of the product types into interfaces and implementing classes, then add the &quot;intersection class&quot; that inherits the interfaces of all of its components.</p>
<pre class="lang-kotlin prettyprint-override"><code>sealed interface Currency {
    sealed interface US : Currency { 
        val dollars: Int
        val cents: Int 
        
        companion object {
            operator fun invoke(dollars: Int, cents: Int) : US =
                USImpl(dollars, cents)
            
            private data class USImpl(override val dollars: Int, override val cents: Int) : US
        }
    }

    ... // same boilerplate for CAD and BTC
}

sealed interface Length {
    ... // same boilerplate as above
}

data class USCurrencyAndLength(
    override val dollars: Int,
    override val cents: Int,
    override val miles: Int,
    override val feet: Int,
    override val inches: Int,
    override val centimeters: Float
) : Currency.US, Length.US
</code></pre>
<p><code>USCurrencyAndLength</code>, as a true &quot;intersection type&quot;, can be assigned to any variable or parameter of type <code>Currency.US</code> or <code>Length.US</code>. Whereas a &quot;product type&quot; like:</p>
<pre class="lang-kotlin prettyprint-override"><code>data class USCurrencyAndLength2(val currency: Currency.US, val length: Length.US)
</code></pre>
<p>cannot be assigned to those types directly, one must assign <code>usCurrencyAndLength2.currency</code> or <code>usCurrencyAndLength2.length</code> respectively.</p>

CC BY-SA 4.0
2024-06-24T04:54:29.037Z
2024-06-24T04:54:29.037Z
tarzh
# Intersection and unions for product and sum types
Posted by: username_0 on 2024-06-24
If you have two product types A×B×C and D×E×F you can construct a new type A×B×C×D×E×F. In typescript (structural typing) this is called intersection &. Should this operation be called something different in nominally typed languages? (since the intersection of different nominal types is empty)
If you have two sum types A+B+C and D+E+F you can construct a new type A+B+C+D+E+F. In typescript (structural typing) this is called union |.
Do any languages have built in support for these operations? (aside for golang (product types only) and typescript)
In C you can always do
struct A;
struct B;
struct C { A a; B b; };

In rust you can do
enum EnumA {
    VariantA1,
    VariantA2,
}
enum EnumB {
    VariantB1,
    VariantB2,
}
enum CombinedEnum {
    A(EnumA),
    B(EnumB),
}

However, this doesn't take advantage of the weak associativity of product and sum types i.e. (A×B)×(C×D)=A×B×C×D, but you get stuck working with (A×B)×(C×D). What languages allow you to take advantage of this associativity? What should this be called? For example Golang allows you to do struct embedding
type TypeA struct {
    PropertyA string
}
type TypeB struct {
    PropertyB int
}
type CombinedType struct {
    TypeA
    TypeB
}

It was pointed out that typescript's & does not really a product operator, neither is typescript's | really a sum operator.
**Tags:** algebraic-data-types
> **Comments:**
> **username_1**: Is there a reason to expect them to be called something different in a nominal environment? Union types are union types. When you say "type", do you mean something constructible (e.g. a Java class), or only a descriptor of values (e.g. a Java interface)?  
> **username_0**: In c it would make sense that a union of union types produce a union. But an intersection of product types produce a product type. It always strikes everyone as unintuitive. Maybe product types should be called intersection types  
> **username_2**: You might enjoy reading *Types and Programming Languages* - there are several sections that walk through different implementations of products and sums, from both a nominal viewpoint and a structural viewpoint.  
> **username_3**: Notice that TypeScript's intersection types are not product types at all. Compare `A×A` to `A&A`  
> **username_4**: Python's static typing system supports Union types (in modern language spelled as `A | B`) and looks for Intersection types for last [8 years](https://github.com/python/typing/issues/213).  
> **username_5**: Typescript's intersection types (`&`) are not product types, and its union types (`|`) are not sum types. Notably, `T & T` and `T | T` are equal to `T`, but these are not generally true of product or sum types. It's also not generally true that an intersection of nominal types is empty, e.g. `Serializable & Cloneable` in Java.  
> **username_5**: It's worth noting that mathematically, the Cartesian product `×` is not literally associative according to its set-theoretic definition, but there is an easy bijection between the products `A×(B×C)` and `(A×B)×C`, so nobody ever really cares about the formal difference, and we write `A×B×C` and tuples like `(a, b, c)` because everybody agrees it is a better notation than always writing `(a, (b, c))`. Likewise, the disjoint union of sets is not literally associative based on its definition, but mathematically it's often convenient to abuse notation and pretend it is.  
> **username_0**: @kaya3 yes, ChatGPT said it was called: associativity up to isomorphism or weak associativity  
> **username_5**: Was it ChatGPT that said Typescript's `&` is a product type, or `|` is a sum type? Do yourself a favour, and don't ask ChatGPT this kind of thing.  
---
## 2 answers
### Answer by username_7 on 2024-06-24. Score: 4 | Accepted answer
This is somewhat possible in OOP languages that have both sealed interfaces and "records" (AKA "data classes", "case classes"), such as Java 21, Scala, and Kotlin. The caveat is that you must own all of the types, i.e. they can't be in a dependency or standard library, or you will have to make wrappers to put them in the union or intersection.
For example, let's say you have the following "algebraic data types" (example is Kotlin, but Scala and Java can do the same with different syntax):
sealed interface Currency {
    data class US(val dollars: Int, val cents: Int) : Currency
    data class CAD(val dollars: Int, val cents: Int) : Currency
    data class BTC(val bitcoins: Float) : Currency
}

sealed interface Length {
    data class US(val miles: Int, val feet: Int, val inches: Int, val centimeters: Float) : Length
    data class Euro(val meters: Float) : Length
}

To define a union, define a sealed interface that is inherited by all members, like so:
sealed interface Quantity {}

sealed class Currency : Quantity { ... }

sealed class Length : Quantity { ... }

You can still do everything with Currency and Length that you could if they weren't part of the union. But now you can also define variables of type Quantity that can be inhabited by both currencies and lengths. And importantly, like a true "union type" and unlike a "sum type" (your Rust example), they don't have to be manually wrapped in Quantity.Currency and Quantity.Length. You can even pattern-match on the union, and then further pattern match on the "sum type"s.
when (quantity) {
  is Currency -> when (val currency = quantity) {
    is Currency.US -> ...
    is Currency.CAD -> ...
    is Currency.BTC -> ...
  }
  is Length -> when (val length = quantity) {
    is Length.US -> ...
    is Length.Euro -> ...
  }
}

Defining an "intersection" type is more complicated, and doesn't preserve every functionality of the product types; specifically, you'll no longer be able to de-structure them, although you can access their fields by name. To define an intersection, split all of the product types into interfaces and implementing classes, then add the "intersection class" that inherits the interfaces of all of its components.
sealed interface Currency {
    sealed interface US : Currency { 
        val dollars: Int
        val cents: Int 
        
        companion object {
            operator fun invoke(dollars: Int, cents: Int) : US =
                USImpl(dollars, cents)
            
            private data class USImpl(override val dollars: Int, override val cents: Int) : US
        }
    }

    ... // same boilerplate for CAD and BTC
}

sealed interface Length {
    ... // same boilerplate as above
}

data class USCurrencyAndLength(
    override val dollars: Int,
    override val cents: Int,
    override val miles: Int,
    override val feet: Int,
    override val inches: Int,
    override val centimeters: Float
) : Currency.US, Length.US

USCurrencyAndLength, as a true "intersection type", can be assigned to any variable or parameter of type Currency.US or Length.US. Whereas a "product type" like:
data class USCurrencyAndLength2(val currency: Currency.US, val length: Length.US)

cannot be assigned to those types directly, one must assign usCurrencyAndLength2.currency or usCurrencyAndLength2.length respectively.
---
### Answer by username_6 on 2024-06-24. Score: 1
For product types, to make the operation associative and commutative, I don't know if there are other ways, but the obvious solution is to make every field having a natural name.

Using the field name as the natural name, is probably not a good design in modern statically typed languages. In dynamically typed languages, it seems inferior to just use no types at all, and check whether the field names exist in situations that need to check the type.

It makes more sense to use a combination of the field name and the type as the natural name, where the later one could be omitted if no ambiguity is found. We could see it's just inheritance. (Inheritance marked as `virtual` in C++, and interfaces in C# and Java, would make the operation also idempotent.) It's just not easy to use the product as an actual type without some advanced templating. But you could add every type as a constraint in templated code where a product type is expected.

For sum types, there is usually a "top type" that is the sum of all types, or at least all types with a vtable. The vtable pointer works better than enums for this purpose, as the sequential order is not predetermined. Sum types of exact types are better implemented as subsets of the top type, by adding constraints. But again, it is usually not easy to use as an actual type.

The reason they are not directly supported as types, might be because of types usually describe exact memory arrangements, but the sum and product types are abstract concepts that ignored such details. But they came back with other more abstract concepts in programming languages that are just not called types.
> **Comments:**
> **username_0** (2024-06-24): You wouldn't make a product commutative would you? It would be natural just to preserve the order. I cant see memory arrangements being the reason why rust does not do enum embedding similar to golang's struct embedding  
---
