Tipo famiglie Haskell e argomenti fittizi
-
19-09-2019 - |
Domanda
Ho fatto una funzione simile a array
di NumPy. Converte le liste di array, liste di liste di array 2d, ecc.
Funziona in questo modo:
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 non (Int, Int)
perché io non conosco un modo programmatico per aumentare la lunghezza di una tupla. (Domanda lato:? C'è modo)
La codifica di esso era scomodo e ho dovuto fare un "workaround" (passando intorno argomenti fittizi per funzioni) per farlo funzionare. Mi chiedo se c'è un modo migliore.
Quindi, ecco il codice, interrotta con i dettagli del brutto soluzioni alternative:
{-# 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
"dovrebbe" essere :: ListOfIndex i a -> (i, i)
. E allo stesso modo per acFlatten
. Ciascuno è data una variabile dummy (undefined
è sempre il valore dato) perché altrimenti non potrei farlo per compilare: (
arrFromNestedLists :: forall i a. ArrConv i => ListOfIndex i a -> Array i a
arrFromNestedLists lst =
listArray
(acBounds (undefined :: a) lst)
(acFlatten (undefined :: i) lst)
Sopra è l'argomento undefined
fittizio che passa sul posto di lavoro. Racconta la GHC quale istanza di ListOfIndex
da utilizzare.
instance ArrConv () where
acBounds _ = const ((), ())
acFlatten _ = (: [])
La funzione di seguito avrebbe dovuto essere la funzione acBounds
in un'istanza di ArrConv
, ed è dichiarata fuori solo perché ho bisogno di usare ScopedTypeVariables
e non so come posso farlo in una funzione in una definizione di caso ..
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))
Soluzione
Il motivo che gli argomenti extra per acBounds e acFlatten sono necessaria è che i tipi a
e i
non possono essere recuperati rispettivamente da ListOfIndex i a -> (i, i)
e ListOfIndex i a -> [a]
. Una soluzione è quella di combinare i due metodi in un metodo di tipo acArgs
ListOfIndex i a -> ((i, i), a)
. Ora l'unico problema è utilizzare nel caso di (Int, i)
in un modo che impedisce controllore dei tipi di generalizzare suo tipo troppo causando lo stesso problema di prima (per esempio, non possiamo utilizzare semplicemente 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
Altri suggerimenti
Se si desidera mantenere acBounds
e acFlatten
separata, è possibile aggiungere una tipo a livello di tag nofollow argomento ad esso, vale a dire acBounds
avrebbe tipo acBounds :: Proxy a -> ListOfIndex i a -> (i, i)
. Ciò elimina la necessità per gli argomenti undefined
, dal momento che si può solo passare (Proxy :: SomeConcreteType)
ad esso; e acBounds
non ha modo di estrarre tutte le informazioni utili a livello di valore da esso, dal momento che è isomorfo (in modo non tipizzato) per il tipo di unità.