famílias tipo Haskell e argumentos falsos
-
19-09-2019 - |
Pergunta
Eu fiz uma função semelhante à array
de numpy. Ele converte listas para matrizes, listas de listas para matrizes 2D, etc.
Ele funciona assim:
ghci> arrFromNestedLists ["hello", "world"] :: Array (Int, (Int, ())) Char
array ((0,(0,())),(1,(4,()))) [((0,(0,())),'h'),((0,(1,())),'e'),((0,(2,())),'l'),((0,(3,())),'l'),((0,(4,())),'o'),((1,(0,())),'w'),((1,(1,())),'o'),((1,(2,())),'r'),((1,(3,())),'l'),((1,(4,())),'d')]
(Int, (Int, ()))
e não (Int, Int)
porque eu não sei de uma maneira programática para aumentar o comprimento de uma tupla. (Pergunta lado:? Há forma)
A codificação de que era estranho e eu tive que fazer uma "solução alternativa" (passando em torno de argumentos falsos para funções) para que ele funcione. Gostaria de saber se há uma maneira melhor.
Então, aqui está o código, interrompida com detalhes das soluções feias:
{-# LANGUAGE FlexibleInstances, ScopedTypeVariables, TypeFamilies #-}
type family ListOfIndex i a
type instance ListOfIndex () a = a
type instance ListOfIndex (Int, i) a = [ListOfIndex i a]
class Ix i => ArrConv i where
acBounds :: a -> ListOfIndex i a -> (i, i)
acFlatten :: i -> ListOfIndex i a -> [a]
acBounds
"deveria" ser :: ListOfIndex i a -> (i, i)
. E da mesma forma para acFlatten
. Cada um é dada uma variável dummy (undefined
é sempre o valor dado) porque senão eu não poderia obtê-lo para compilar: (
arrFromNestedLists :: forall i a. ArrConv i => ListOfIndex i a -> Array i a
arrFromNestedLists lst =
listArray
(acBounds (undefined :: a) lst)
(acFlatten (undefined :: i) lst)
Acima é o argumento fictício undefined
passando no trabalho. Conta a GHC qual instância ListOfIndex
para uso.
instance ArrConv () where
acBounds _ = const ((), ())
acFlatten _ = (: [])
A seguir função deve ter sido a função acBounds
em uma instância do ArrConv
, e é declarada fora só porque eu preciso usar ScopedTypeVariables
e eu não sei como eu posso fazer isso em uma função em uma definição instância ..
acSucBounds
:: forall a i. ArrConv i
=> a -> [ListOfIndex i a] -> ((Int, i), (Int, i))
acSucBounds _ lst =
((0, inStart), (length lst - 1, inEnd))
where
(inStart, inEnd) = acBounds (undefined :: a) (head lst)
instance ArrConv i => ArrConv (Int, i) where
acBounds = acSucBounds
acFlatten _ = concatMap (acFlatten (undefined :: i))
Solução
A razão que os argumentos extras para acBounds e acFlatten são necessários é que o a
tipos e i
não podem ser recuperados a partir ListOfIndex i a -> (i, i)
e ListOfIndex i a -> [a]
respectivamente. Uma solução alternativa é a de combinar os dois métodos em um método de tipo acArgs
ListOfIndex i a -> ((i, i), a)
. Agora, o único problema é usá-lo no caso de (Int, i)
de uma forma que impede a typechecker de generalizar seu tipo demasiado causando o mesmo problema de antes (por exemplo, não podemos simplesmente usar fst . acArgs
).
{-# LANGUAGE TypeFamilies, FlexibleInstances #-} import Data.Array type family ListOfIndex i a type instance ListOfIndex () a = a type instance ListOfIndex (Int, i) a = [ListOfIndex i a] class Ix i => ArrConv i where acArgs :: ListOfIndex i a -> ((i, i), [a]) instance ArrConv () where acArgs x = (((), ()), [x]) instance ArrConv i => ArrConv (Int, i) where acArgs lst = (((0, inStart), (length lst - 1, inEnd)), args >>= snd) where args = map acArgs lst (inStart, inEnd) = fst (head args) arrFromNestedLists :: ArrConv i => ListOfIndex i a -> Array i a arrFromNestedLists = uncurry listArray . acArgs
Outras dicas
Se você quiser manter acBounds
e acFlatten
separado, você pode adicionar um de nível tipo de argumento tag a ele, ou seja, acBounds
teria tipo acBounds :: Proxy a -> ListOfIndex i a -> (i, i)
. Isso elimina a necessidade dos argumentos undefined
, pois você pode apenas passar (Proxy :: SomeConcreteType)
a ele; e acBounds
não tem nenhuma maneira de extrair qualquer informação de nível de valor útil a partir dele, uma vez que é isomórfico (de forma não tipificada) para o tipo de unidade.