consideri la semplice definizione di un vettore di lunghezza-indicizzato:Rappresentazione runtime speciale di [] tipo?
data Nat = Z | S Nat
infixr 5 :>
data Vec (n :: Nat) a where
V0 :: Vec 'Z a
(:>) :: a -> Vec n a -> Vec ('S n) a
Naturalmente sarebbe ad un certo punto hanno bisogno la seguente funzione:
vec2list :: Vec n a -> [a]
Tuttavia, questa funzione è in realtà solo l'identità di fantasia. Credo che le rappresentazioni runtime di questi due tipi siano le stesse, quindi
vec2list :: Vec n a -> [a]
vec2list = unsafeCoerce
dovrebbe funzionare. Ahimè, non è così:
>vec2list ('a' :> 'b' :> 'c' :> V0)
""
Ogni input restituisce semplicemente la lista vuota. Quindi presumo che manchi la mia comprensione. Per provarlo, definisco il seguente:
data List a = Nil | Cons a (List a) deriving (Show)
vec2list' :: Vec n a -> List a
vec2list' = unsafeCoerce
test1 = vec2list' ('a' :> 'b' :> 'c' :> V0)
data SomeVec a = forall n . SomeVec (Vec n a)
list'2vec :: List a -> SomeVec a
list'2vec x = SomeVec (unsafeCoerce x)
Sorprendentemente questo funziona! Di certo non è un problema con il GADT (il mio pensiero iniziale).
Penso che il tipo List
sia identico in fase di esecuzione a []
. Provo a testare anche questo:
list2list :: [a] -> List a
list2list = unsafeCoerce
test2 = list2list "abc"
e funziona! Sulla base di questo fatto, devo concludere che [a]
e List a
devono avere la stessa rappresentazione runtime. Eppure, il seguente
list2list' :: List a -> [a]
list2list' = unsafeCoerce
test3 = list2list' (Cons 'a' (Cons 'b' (Cons 'c' Nil)))
non funziona. list2list'
restituisce sempre sempre la lista vuota. Credo che "avere identiche rappresentazioni di runtime" debba essere una relazione simmetrica, quindi questo non sembra avere senso.
Ho iniziato a pensare che forse c'è qualcosa di divertente con i tipi "primitivi" - ma ho sempre creduto che lo []
sia solo sintatticamente speciale, non semanticamente. Sembra che sia il caso:
data Pair a b = Pair a b deriving (Show, Eq, Ord)
tup2pair :: (a,b) -> Pair a b
tup2pair = unsafeCoerce
pair2tup :: Pair a b -> (a,b)
pair2tup = unsafeCoerce
I primi lavori di funzione e la seconda non lo fa - come il nel caso di List
e []
. Anche se in questo caso, pair2tup
segfaults anziché restituire sempre la lista vuota.
Sembra essere coerentemente asimmetrico rispetto ai tipi che utilizzano la sintassi "built-in". Torna all'esempio Vec
, il seguente
list2vec :: [a] -> SomeVec a
list2vec x = SomeVec (unsafeCoerce x)
funziona bene pure! Il GADT non è davvero speciale.
La domanda è: in che modo le rappresentazioni di runtime dei tipi che utilizzano la sintassi "incorporata" differiscono da quelle che non lo sono?
In alternativa, come si scrive una coercizione a costo zero da Vec n a
a [a]
? Questo non risponde alla domanda, ma risolve il problema.
Il test è stato eseguito con GHC 7.10.3.
Come notato da un commentatore, questo comportamento è presente solo nell'interpretazione. Una volta compilato, tutte le funzioni funzionano come previsto. La domanda vale ancora, solo per la rappresentazione di runtime nell'interpretazione di.
"Funziona bene" in combinazione con "unsafeCoerce" è come indicare una rovina e dire "guarda, è ancora in piedi". È come usare un 'reinterpret_cast (...)' in C++. Con la ragione corretta, funzionerà, ma d'altra parte GHC non dedurrà alcuna istanza di 'Coercible' per nessuno dei tuoi tipi. Detto questo, IIRC 'RuntimeRep' cambia in GHC 8 (vedi' GHC.Types') a causa di [levity polymorphism] (http://stackoverflow.com/q/35318562/1139697). –
Zeta
Per rispondere alla tua seconda domanda: 'newtype Vec (n :: Nat) a = MkVec [a]' insieme a 'v0 :: Vec 'Z a; v0 = [] ',' (|>) :: a -> Vec n a -> Vec ('S n) a; (|>) x = coercizione. (X:) . coerce', dove 'coerce' è di' Data.Coerce'. Il compilatore deduce quindi correttamente che 'n :: Nat' è solo un tipo fantasma. – Zeta
@Zeta Come hai detto tu, con il ragionamento corretto dovrebbe funzionare. Non penso che questo sia esplicitamente uno degli usi "approvati", ma in pratica le rappresentazioni di runtime dei tipi "moralmente uguali" sono * di solito * le stesse, tranne apparentemente nei casi precedenti. Questa domanda non riguarda la progettazione di alto livello del codice Haskell. Si tratta del fegato di GHC. La domanda alternativa è in realtà una sorta di trucco: sono abbastanza sicuro che sia impossibile senza "unsafeCoerce", ma non voglio scoraggiare qualcuno dal provare a dimostrarmi sbagliato. – user2407038