programare funcțională - Diferența dintre tipurile union și tipurile union discriminate în TypeScript/F#


Așadar, citeam documentația oficială a Typescript despre tipurile de uniune și mă gândeam că este la fel ca „uniunile discriminate” în F# (desigur, ele au o sintaxă diferită, dar același concept), având în vedere că am experiență în F# și faptul că ambele sunt susținute de Microsoft. Dar, uitându-mă la documentație, F# nu face cu adevărat o distincție între „tipurile de uniune” și „uniunile discriminate”: https://fsharpforfunandprofit.com/posts/discriminated-unions/

Totuși, Typescript face o distincție între aceste două concepte:

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

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

Deci mă întrebam dacă există cu adevărat o distincție în conceptele în sine sau este doar un concept dependent de limbă?

Ceea ce înțeleg până acum este că tipurile de uniune în F# sunt, de asemenea, uniuni discriminate pentru că poți discrimina tipul de uniune folosind expresii match și deconstruirea acestuia.

Totuși, nu poți face discriminarea cu Typescript deoarece limbajul nu oferă o expresie specifică pentru a face asta, așa că trebuie să discriminezi după o valoare pe care toate tipurile union o au, discriminantul. Este corect asta?

Răspuns #1

Diferența principală este că, tipul Union din Typescript este de fapt un superset al Union-ului discriminat din F#.

TypeScript Union Type=tip de uniune nefasonat.
F# Discriminated Union=tip de uniune fasonat.

Cu alte cuvinte, fiecare uniune discriminată care poate fi modelată în F# poate fi modelată izomorfic în tipul de uniune din Typescript, însă inversul nu este adevărat.

De exemplu, următoarea uniune discriminată în F#:

type a' Option = Some of 'a | None 

Poate fi modelat izomorf în Typescript ca:

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

Totuși, următorul tip de uniune în Typescript nu poate fi modelat izomorf în F#:

type UserInput = number | string

Principala diferență aici este că tipul union din TypeScript nu trebuie să fie etichetat, însă tipul union din F# trebuie să fie etichetat.

Astfel, putem vedea că TypeScript este de fapt mai flexibil decât F#, totuși acest lucru nu vine fără cost, uniunile netaguite sunt de fapt cu găuri, ceea ce înseamnă că există unele uniuni de tip unde TypeScript va eșua să facă verificarea tipului.

Este ca și cum calculul lambda netipat este un superset al calculului lambda tipizat, dar calculul lambda tipizat este mult mai ușor de demonstrat, nu-i așa.

Răspunsul #2

Operanzii dintr-o uniune de tip (A | B) sunt ambele tipuri, în timp ce cazurile dintr-o uniune discriminată type U=A | B sunt ambele constructori pentru tipul U și nu sunt tipuri în sine. Valorile de tip U sunt etichetate la timpul de rulare astfel încât să poți distinge între cazurile posibile.

O consecință este că uniunile discriminate pot fi imbricate într-un mod în care tipurile de uniune nu pot. Valorile opționale de un anumit tip A într-un sistem de tipuri de uniune ar putea fi reprezentate ca

type A? = (A | null)

unde null este tipul singleton pentru valoarea null.

Cu o uniune discriminată, este de obicei reprezentată ca

type a' option = Some of 'a | None 

Cu această formulare valoarea

let o: int option option = Some None

nu poate fi reprezentat cu tipul union deoarece (A?)? == (A | null) | null == A | null == A?