Converter instâncias de família de tipos em Int
-
14-12-2019 - |
Pergunta
Eu tenho este código:
type family Id obj :: *
type instance Id Box = Int
E eu quero fazer isso para que eu possa sempre obter um Int da família de tipos Id.Reconheço que será necessária uma conversão.
Achei que talvez criar uma classe funcionasse:
class IdToInt a where
idToInt :: Id a -> Int
instance IdToInt Box where
idToInt s = s
E isso realmente compila.Mas quando tento usá-lo:
testFunc :: Id a -> Int
testFunc x = idToInt x
Eu recebo um erro:
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
Então, como posso criar uma conversão para um ID de família de tipos para obter um Int?
Com base na resposta de ehird, tentei o seguinte, mas também não funcionou:
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á erro:
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
Solução
Como outros apontaram, o problema é que o compilador não consegue descobrir qual a
usar.As famílias de dados são uma solução, mas uma alternativa que às vezes é mais fácil de trabalhar é usar um tipo testemunha.
Mude sua turma para
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)
A pergunta que você precisa responder é: para qualquer Id
, existe apenas um tipo a
deve aparecer com?Ou seja, existe uma correspondência um para um entre a
areia Id a
é?Nesse caso, as famílias de dados são a abordagem correta.Caso contrário, você pode preferir uma testemunha.
Outras dicas
Você não pode.Você precisará testFunc :: (IdToInt a) => Id a -> Int
.As famílias do tipo são abertas, então qualquer pessoa pode declarar
type instance Id Blah = ()
a qualquer momento e não oferece função de conversão.A melhor coisa a fazer é colocar a família de tipos na classe:
class HasId a where
type Id a
idToInt :: Id a -> Int
instance IdToInt Box where
type Id Box = Int
idToInt s = s
Você ainda precisará do contexto.
Você não pode usar uma função do tipo IdToInt a => Id a -> Int
porque não há como determinar que tipo a
é.O exemplo a seguir demonstra isso.
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
Porque Id () = Id Char = Int
, o tipo de idToInt
no contexto acima é Int -> Int
, que é igual a Id () -> Int
e Id Char -> Int
.Lembre-se de que os métodos sobrecarregados são escolhidos com base no tipo.Ambas as instâncias de classe definem idToInt
funções que possuem tipo Int -> Int
, portanto, o verificador de tipo não pode decidir qual usar.
Você deve usar uma família de dados em vez de uma família de tipos e declarar instâncias de newtype.
data family Id a :: *
newtype instance Id () = IdUnit Int
newtype instance Id Char = IdChar Int
Com uma instância newtype, Id ()
e Id Char
são ambos ints, mas têm tipos diferentes.O tipo de um Id
informa ao verificador de tipo qual função sobrecarregada usar.