Perché (o perché no) i tipi esistenziali sono considerati cattive pratiche nella programmazione funzionale?


43

Quali sono alcune tecniche che potrei usare per refactificare costantemente il codice rimuovendo la dipendenza da tipi esistenziali? In genere questi vengono utilizzati per squalificare costruzioni indesiderate del tuo tipo e per consentire il consumo con una minima conoscenza del tipo dato (o almeno così è la mia comprensione).

Qualcuno ha escogitato un modo semplice e coerente per rimuovere la dipendenza da questi nel codice che mantiene ancora alcuni dei vantaggi? O almeno qualche modo di scivolare in un'astrazione che ne consenta la rimozione senza richiedere un significativo abbandono del codice per far fronte all'alterazione?

Puoi leggere di più sui tipi esistenziali qui ("se osi ..").


8
@RobertHarvey Ho trovato il post sul blog che mi ha fatto pensare a questa domanda la prima volta: Haskell Antipattern: Existential Typeclass . Inoltre, l'articolo citato da Joey Adams descrive alcuni problemi con esistenziali nella Sezione 3.1. Se hai argomenti contrari, condividili.
Petr Pudlák,

5
@ PetrPudlák: tieni presente che l'antipasto non ci sono tipi esistenziali in generale, ma un loro uso particolare quando qualcosa di più semplice e più facile (e meglio supportato in Haskell) farebbe lo stesso lavoro.
CA McCann,

10
Se vuoi sapere perché l'autore di un determinato post sul blog ha espresso un'opinione, allora la persona che dovresti probabilmente chiedere è l'autore.
Eric Lippert,

6
@Ptolemy Questa è una questione di opinione. Usando Haskell per anni, non riesco quasi a immaginare di usare un linguaggio funzionale che non ha un sistema di caratteri forte.
Petr Pudlák

4
@Ptolemy: il mantra di Haskell è "se compila funziona", il mantra di Erlang è "lascialo andare in crash". La digitazione dinamica è buona come colla, ma personalmente non costruirò le cose usando solo la colla.
Den

Risposte:


8

I tipi esistenziali non sono realmente considerati cattive pratiche nella programmazione funzionale. Penso che ciò che ti ha inciampato sia che uno degli usi più comunemente citati per gli esistenziali è l' antipasto di tabella dei tipi esistenziali , che molte persone credono sia una cattiva pratica.

Questo modello viene spesso tracciato come risposta alla domanda su come avere un elenco di elementi tipizzati in modo eterogeneo che implementano tutti la stessa classe di caratteri. Ad esempio, potresti voler avere un elenco di valori con Showistanze:

{-# LANGUAGE ExistentialTypes #-}

class Shape s where
   area :: s -> Double

newtype Circle = Circle { radius :: Double }
instance Shape Circle where
   area (Circle r) = pi * r^2

newtype Square = Square { side :: Double }
    area (Square s) = s^2

data AnyShape = forall x. Shape x => AnyShape x
instance Shape AnyShape where
    area (AnyShape x) = area x

example :: [AnyShape]
example = [AnyShape (Circle 1.0), AnyShape (Square 1.0)]

Il problema con codice come questo è questo:

  1. L'unica operazione utile che puoi eseguire su un AnyShapeè ottenere la sua area.
  2. È ancora necessario utilizzare il AnyShapecostruttore per inserire uno dei tipi di forma nel AnyShapetipo.

Quindi, a quanto pare, quel pezzo di codice non ti dà davvero nulla di quello che questo più corto non ha:

class Shape s where
   area :: s -> Double

newtype Circle = Circle { radius :: Double }
instance Shape Circle where
   area (Circle r) = pi * r^2

newtype Square = Square { side :: Double }
    area (Square s) = s^2

example :: [Double]
example = [area (Circle 1.0), area (Square 1.0)]

Nel caso di classi multi-metodo, lo stesso effetto può essere generalmente ottenuto in modo più semplice utilizzando una codifica "record di metodi", anziché utilizzare una classe di tipi come Shape, si definisce un tipo di record i cui campi sono i "metodi" del Shapetipo e scrivi funzioni per convertire le tue cerchie e i tuoi quadrati in Shapes.


Ma ciò non significa che i tipi esistenziali siano un problema! Ad esempio, in Rust hanno una caratteristica chiamata oggetti tratto che le persone spesso descrivono come un tipo esistenziale rispetto a un tratto (versioni di Rust delle classi di tipi). Se i caratteri tipografici esistenziali sono un antipasto in Haskell, significa che Rust ha scelto una cattiva soluzione? No! La motivazione nel mondo Haskell riguarda la sintassi e la convenienza, non proprio i principi.

Un modo più matematico per dirlo consiste nel fatto che i AnyShapetipi dall'alto e Doublesono isomorfi: esiste una "conversione senza perdita di dati" tra loro (beh, a parte la precisione in virgola mobile):

forward :: AnyShape -> Double
forward = area

backward :: Double -> AnyShape
backward x = AnyShape (Square (sqrt x))

Quindi, a rigor di termini, non stai guadagnando o perdendo alcun potere scegliendo l'uno contro l'altro. Ciò significa che la scelta dovrebbe essere basata su altri fattori come la facilità d'uso o le prestazioni.


E tieni presente che i tipi esistenziali hanno altri usi al di fuori di questo esempio di elenchi eterogenei, quindi è bene averli. Ad esempio, il STtipo di Haskell , che ci consente di scrivere funzioni esternamente pure ma che utilizzano internamente operazioni di mutazione della memoria, utilizza una tecnica basata su tipi esistenziali al fine di garantire sicurezza al momento della compilazione.

Quindi la risposta generale è che non esiste una risposta generale. Gli usi dei tipi esistenziali possono essere giudicati solo nel contesto e le risposte possono essere diverse a seconda delle caratteristiche e della sintassi fornite da lingue diverse.


Questo non è un isomorfismo.
Ryan Reich,

2

Non ho familiarità con Haskell, quindi cercherò di rispondere alla parte generale della domanda come sviluppatore funzionale non accademico C #.

Dopo aver letto un po ', si scopre che:

  1. I caratteri jolly Java sono simili ai tipi esistenziali:

    La differenza tra i tipi esistenziali di Scala e il jolly di Java per esempio

  2. I caratteri jolly non sono implementati completamente in C #: la varianza generica è supportata, ma la varianza del sito di chiamata non è:

    Generici C #: caratteri jolly

  3. Potresti non aver bisogno di questa funzione ogni giorno, ma quando lo fai la sentirai (ad es. Dover introdurre un tipo aggiuntivo per far funzionare le cose):

    Caratteri jolly nei vincoli generici C #

Sulla base di queste informazioni, i tipi / caratteri jolly esistenziali sono utili se implementati correttamente e di per sé non c'è nulla di sbagliato, ma probabilmente possono essere utilizzati in modo improprio proprio come le altre funzionalità linguistiche.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.