Converti istanze di famiglia tipo per int
-
14-12-2019 - |
Domanda
Ho questo codice:
type family Id obj :: *
type instance Id Box = Int
.
E voglio farlo in modo da poter sempre ottenere un'inT dalla famiglia Tipo ID.Riconosco che sarà richiesta una conversione.
Ho pensato che forse creare una classe funzionerebbe:
class IdToInt a where
idToInt :: Id a -> Int
instance IdToInt Box where
idToInt s = s
.
e questo in realtà compila.Ma quando provo a usarlo:
testFunc :: Id a -> Int
testFunc x = idToInt x
.
I Ottieni errori:
src/Snowfall/Spatial.hs:29:22:
Couldn't match type `Id a0' with `Id a'
NB: `Id' is a type function, and may not be injective
In the first argument of `idToInt', namely `x'
In the expression: idToInt x
In an equation for `testFunc': testFunc x = idToInt x
.
Allora, come posso creare una conversione per un ID familiare di tipo per ottenere un INT?
Basato sulla risposta di Ehird, ho provato quanto segue ma non funziona neanche:
class IdStuff a where
type Id a :: *
idToInt :: Id a -> Int
instance IdStuff Box where
type Id Box = Int
idToInt s = s
testFunc :: (IdStuff a) => Id a -> Int
testFunc x = idToInt x
.
Dà errore:
src/Snowfall/Spatial.hs:45:22:
Could not deduce (Id a0 ~ Id a)
from the context (IdStuff a)
bound by the type signature for
testFunc :: IdStuff a => Id a -> Int
at src/Snowfall/Spatial.hs:45:1-22
NB: `Id' is a type function, and may not be injective
In the first argument of `idToInt', namely `x'
In the expression: idToInt x
In an equation for `testFunc': testFunc x = idToInt x
. Soluzione
Come altri hanno sottolineato, il problema è che il compilatore non può capire quale a
da utilizzare.Le famiglie di dati sono una soluzione, ma un'alternativa a volte è più facile da lavorare è usare un testimone di tipo.
Cambia la tua classe su
class IdToInt a where
idToInt :: a -> Id a -> Int
instance IdToInt Box where
idToInt _ s = s
-- if you use this a lot, it's sometimes useful to create type witnesses to use
box = undefined :: Box
-- you can use it like
idToInt box someId
-- or
idToInt someBox (getId someBox)
.
La domanda di cui è necessario rispondere è, per qualsiasi dato Id
, c'è solo un tipo a
con cui dovrebbe apparire?Cioè, c'è una corrispondenza da una a una corrispondenza tra a
s e Id a
s?In tal caso, le famiglie di dati sono l'approccio corretto.Se no, potresti preferire un testimone.
Altri suggerimenti
non puoi.Avrai bisogno di testFunc :: (IdToInt a) => Id a -> Int
.Tipo Le famiglie sono aperte, quindi chiunque può dichiarare
type instance Id Blah = ()
.
in qualsiasi momento e non offri alcuna funzione di conversione.La cosa migliore da fare è mettere la famiglia del tipo nella classe:
class HasId a where
type Id a
idToInt :: Id a -> Int
instance IdToInt Box where
type Id Box = Int
idToInt s = s
.
Avrai ancora bisogno del contesto, però.
Non è possibile utilizzare una funzione di tipo IdToInt a => Id a -> Int
perché non c'è modo di determinare quale tipo a
è.L'esempio seguente lo dimostra.
type family Id a :: *
type instance Id () = Int
type instance Id Char = Int
class IdToInt a where idToInt :: Id a -> Int
instance IdToInt () where idToInt x = x + 1
instance IdToInt Char where idToInt x = x - 1
main = print $ idToInt 1
.
Poiché Id () = Id Char = Int
, il tipo di generatori di generazioneGode nel contesto di cui sopra è idToInt
, che è uguale a Int -> Int
e Id () -> Int
.Ricorda che i metodi sovraccaricati vengono scelti in base al tipo.Entrambe le istanze di classe definiscono le funzioni Id Char -> Int
che hanno tipo idToInt
, quindi il controllo del tipo non può decidere quale utilizzare.
Dovresti utilizzare una famiglia dati invece di una famiglia di tipo e dichiarare le istanze di Newtype.
data family Id a :: *
newtype instance Id () = IdUnit Int
newtype instance Id Char = IdChar Int
.
Con un'istanza di newtype, Int -> Int
e Id ()
è entrambi gli interni, ma hanno tipi diversi.Il tipo di un Id Char
informa il controllo del tipo che funziona la funzione di sovraccarico da utilizzare.