FraK
FraK

Reputation: 1089

Difference union types and discriminated unions typescript/F#

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?

Upvotes: 6

Views: 3600

Answers (2)

Wong Jia Hau
Wong Jia Hau

Reputation: 3069

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.

Upvotes: 9

Lee
Lee

Reputation: 144176

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?

Upvotes: 7

Related Questions