3
votes

So I was reading the official docs of Typescript about union types, and I was thinking that it is the same as "discriminated unions" in F# (granted they have different syntax but same concept), as I have a F# background and given the fact that both are backed by Microsoft. But looking at the docs, F# doesn't really make a distinction between "union types" and "discriminated unions": https://fsharpforfunandprofit.com/posts/discriminated-unions/

However, Typescript does make a distinction between these two concepts:

Union types: https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types

Discriminated Unions: https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions

So I was wondering if there is really a distinction in the concepts themselves or is just some language dependent concept?

What I understand so far is that union types in F# are also discriminated unions because you can discriminate the union type using match expressions and deconstructing.

However, you cannot do the discrimination with Typescript as the language does not provide a specific expression to do that, so yo need to discriminate by a value, that all union types have, the discriminant. Is this correct?

2
Yes, the discriminant in TS is just an abitrarily object property of your choice. With discriminated or tagged unions you don't need this, because the type itself carries this information around. Since the tag isn't erased during the compilation you can pattern match it at runtime.Iven Marquardt

2 Answers

7
votes

The main difference is that, Typescript Union Type is actually a superset of F# Discriminated Union.

TypeScript Union Type = Untagged union type.
F# Discriminated Union = Tagged union type.

In other words, every discriminated union that can be modelled in F# can be modelled isomorphically in Typescript union type, however the reverse is not true.

For example, the following discriminated union in F#:

type a' Option = Some of 'a | None 

Can be modelled isomorphically in Typescript as:

type Option<T> = {tag: 'Some', value: T} | {tag: 'None'}

However, the following Typescript union type cannot be modelled isomorphically in F#:

type UserInput = number | string

The main difference here is TypeScript union type do not need be tagged, however F# union type must be tagged.

Thus, we can see that TypeScript is actually more flexible than F#, however this does not come without cost, untagged union are actually holey, meaning there are some type union where TypeScript will fail to type-check.

It's like untype lambda calculus is superset of typed lambda calculus, but type lambda calculus is much more easier to proof right.

2
votes

The operands in a type union (A | B) are both types, while the cases in a discriminated union type U = A | B are both constructors for the type U and are not types themselves. The values of type U are tagged at runtime so you can distinguish between the possible cases.

One consequence is that discriminated unions can be nested in a way union types might not. Optional values of some type A within a union type system might be represented as

type A? = (A | null)

where null is the singleton type for the null value.

With a discriminated union it is usually represented as

type a' option = Some of 'a | None 

With this formulation the value

let o: int option option = Some None

cannot be represented with the union type since (A?)? == (A | null) | null == A | null == A?