Search code examples
typescriptfunctional-programmingf#union-types

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?


Solution

  • 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.