Konvertieren Sie Typfamilieninstanzen in Int
-
14-12-2019 - |
Frage
Ich habe diesen Code:
type family Id obj :: *
type instance Id Box = Int
Und ich möchte es so gestalten, dass ich immer ein Int aus der Id-Typfamilie erhalten kann.Mir ist bewusst, dass eine Umstellung erforderlich sein wird.
Ich dachte, vielleicht würde es funktionieren, eine Klasse zu erstellen:
class IdToInt a where
idToInt :: Id a -> Int
instance IdToInt Box where
idToInt s = s
Und das wird tatsächlich kompiliert.Aber wenn ich versuche, es zu verwenden:
testFunc :: Id a -> Int
testFunc x = idToInt x
Ich erhalte die Fehlermeldung:
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
Wie kann ich also eine Konvertierung für eine Typfamilien-ID erstellen, um einen Int zu erhalten?
Basierend auf der Antwort von ehird habe ich Folgendes versucht, aber es funktioniert auch nicht:
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
Es gibt einen Fehler:
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
Lösung
Wie andere bereits betont haben, besteht das Problem darin, dass der Compiler nicht herausfinden kann, welche a
benutzen.Datenfamilien sind eine Lösung, aber eine Alternative, mit der man manchmal einfacher arbeiten kann, ist die Verwendung eines Typzeugen.
Ändern Sie Ihre Klasse in
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)
Die Frage, die Sie beantworten müssen, lautet: Für jeden Fall Id
, gibt es nur einen Typ a
es sollte mit erscheinen?Das heißt, gibt es eine Eins-zu-eins-Entsprechung zwischen? a
s und Id a
S?Wenn ja, sind Datenfamilien der richtige Ansatz.Wenn nicht, bevorzugen Sie möglicherweise einen Zeugen.
Andere Tipps
Sie können nicht.Sie benötigen testFunc :: (IdToInt a) => Id a -> Int
.Typfamilien sind offen, also kann jeder deklarieren
jederzeit und geben Sie keine Konvertierungsfunktion an.Das Beste, was man tun soll, ist, die Typfamilie in die Klasse zu bringen:
generasacodicetagpre.Sie benötigen jedoch noch den Kontext.
Sie können eine Funktion vom Typ nicht verwenden IdToInt a => Id a -> Int
weil es keine Möglichkeit gibt, den Typ zu bestimmen a
Ist.Das folgende Beispiel zeigt dies.
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
Weil Id () = Id Char = Int
, die Art von idToInt
im obigen Kontext ist Int -> Int
, was gleich ist Id () -> Int
Und Id Char -> Int
.Denken Sie daran, dass überladene Methoden basierend auf ihrem Typ ausgewählt werden.Beide Klasseninstanzen definieren idToInt
Funktionen, die einen Typ haben Int -> Int
, daher kann der Typprüfer nicht entscheiden, welches verwendet werden soll.
Sie sollten eine Datenfamilie anstelle einer Typfamilie verwenden und neue Typinstanzen deklarieren.
data family Id a :: *
newtype instance Id () = IdUnit Int
newtype instance Id Char = IdChar Int
Mit einer Newtype-Instanz, Id ()
Und Id Char
sind beide Ints, haben aber unterschiedliche Typen.Der Typ eines Id
informiert den Typprüfer darüber, welche überladene Funktion verwendet werden soll.