Sì, è possibile convincere GHC che non si cura del tipo esatto con cui è parametrizzato D
. È un'idea orribile.
{-# LANGUAGE GADTs #-}
import Unsafe.Coerce
data D a = D a deriving (Show)
data Wrap where -- this GADT is equivalent to your `ExistentialQuantification` version
Wrap :: D a -> Wrap
unwrap :: Wrap -> D a
unwrap (Wrap (D a)) = D (unsafeCoerce a)
main = print (unwrap (Wrap $ D "bla") :: D Integer)
Questo è ciò che accade quando eseguo che semplice programma:
e così via, fino a consumo di memoria porta il sistema.
I tipi sono importanti! Se elimini il sistema di tipi, elimini qualsiasi prevedibilità del tuo programma (ad esempio, qualsiasi cosa può accadere, incluso thermonuclear war o il famoso demons flying out of your nose).
Ora, evidentemente avete pensato che i tipi in qualche modo funzionassero in modo diverso. In linguaggi dinamici come Python, e anche in una laurea in linguaggi OO come Java, un tipo è in un certo senso una proprietà che un valore può avere. Quindi, i valori (di riferimento) non portano solo le informazioni necessarie per distinguere i diversi valori di un singolo tipo, ma anche informazioni per distinguere diversi (sotto) tipi. Questo è in molti sensi piuttosto inefficiente. – è una delle ragioni principali per cui Python è così lento e Java ha bisogno di una VM così grande.
In Haskell, i tipi non esistono in fase di esecuzione. Una funzione non sa mai di che tipo hanno i valori con cui sta lavorando. Solo perché il compilatore conosce tutti i tipi che avrà, la funzione non ha bisogno di qualsiasi conoscenza tale – il compilatore lo ha già codificato! (Cioè, a meno che non aggirare con unsafeCoerce
, che, come ho dimostrato è come non sicuro come sembra.)
Se si vuole collegare il tipo come un “ proprietà ” ad un valore, è necessario farlo in modo esplicito e questo è ciò per cui ci sono quei wrapper esistenziali. Tuttavia, di solito ci sono modi migliori per farlo in un linguaggio funzionale. Qual è in realtà l'applicazione che volevi?
Forse è anche utile ricordare cosa significa una firma con risultato polimorfico. unwrap :: Wrap -> D a
non significa “ il risultato è un po 'D a
... e il chiamante meglio non si preoccupa per il utilizzato ”. Questo sarebbe il caso in Java, ma sarebbe piuttosto inutile in Haskell perché non c'è nulla che tu possa fare con un valore di tipo sconosciuto.
Invece significa: per qualsiasi tipoa
il chiamante, questa funzione è in grado di fornire un adeguato valore di D a
. Ovviamente questo è difficile da fornire – senza ulteriori informazioni è altrettanto impossibile fare qualsiasi cosa con un valore di tipo sconosciuto dato. Ma se ci sono già a
valori nei argomenti, o a
è in qualche modo vincolata a una classe tipo (ad es fromInteger :: Num a => Integer -> a
, allora è del tutto possibile e molto utile.
Per ottenere un String
campo – indipendente a
parametro – si può solo operare direttamente sul valore avvolto:
data D a = D
{ dLabel :: String
, dValue :: a
}
data Wrap where Wrap :: D a -> Wrap
labelFromWrap :: Wrap -> String
labelFromWrap (Wrap (D l _)) = l
Per scrivere tali funzioni su più Wrap
genericamente (con qualsiasi accessorio per etichette “ che non interessa a
”), utilizzare il polimorfismo di Rank2 come mostrato nella risposta di n.m.
Penso che il motivo per cui esista a. D a' non è una parte di Haskell è un semplice desiderio di evitare la ridondanza. Questo tipo è equivalente a 'forall r. (forall a. D a -> r) -> r', quindi si può anche usare quello per rappresentare i valori esistenziali. –
@ n.m. mentre in senso stretto non è equivalente, concordo sul fatto che l'unica cosa utile che può essere fatta a un tale valore esistenziale è l'applicazione di una funzione "per tutto a. D a -> r', quindi l'incapacità di esprimere questo esistenziale ad-hoc ha senso - grazie. –