2010-01-11 6 views
5

J'ai créé une fonction similaire à celle de numpy array. Il convertit les listes de tableaux, listes de listes à des tableaux 2d, etc.Familles de type Haskell et arguments fictifs

Il fonctionne comme ceci:

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,())) et non (Int, Int) parce que je ne sais pas d'une façon programatic d'augmenter la longueur un tuple. (question subsidiaire: y at-il une telle manière?)

Le codage de celui-ci était maladroit et j'ai dû faire une "solution de contournement" (en passant autour des arguments factices aux fonctions) pour que cela fonctionne. Je me demande s'il y a un meilleur moyen.

Alors, voici le code, interrompu par les détails des solutions de contournement laid:

{-# 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 "devrait" être :: ListOfIndex i a -> (i, i). Et de même pour acFlatten. Chacun reçoit une variable factice (undefined est toujours la valeur donnée), car sinon je ne pouvais pas le compiler :(

arrFromNestedLists :: forall i a. ArrConv i => ListOfIndex i a -> Array i a 
arrFromNestedLists lst = 
    listArray 
    (acBounds (undefined :: a) lst) 
    (acFlatten (undefined :: i) lst) 

Au-dessus est l'argument undefined fictif qui passe au travail. Il raconte l'GHC quelle instance de ListOfIndex à utiliser.

instance ArrConv() where 
    acBounds _ = const ((),()) 
    acFlatten _ = (: []) 

la fonction ci-dessous aurait dû être la fonction acBounds dans une instance de ArrConv, et est déclarée en dehors seulement parce que je dois utiliser ScopedTypeVariables et je ne sais pas comment je peux le faire dans un fonctionner dans une définition d'instance.

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)) 
+3

"Je ne connais pas de méthode par programme pour augmenter la longueur d'un tuple." Je ne pense pas que tu puisses. C'est un exemple parfait d'une fonction dont le type dépend d'une valeur. Il serait facile de le faire dans une langue typée dépendante comme 'Agda', mais impossible dans Haskell. Peut-être pourriez-vous utiliser 'GADTs 'd'une façon ou d'une autre pour vous donner un comportement dépendant, mais je ne sais pas comment vous le faites. –

+0

Peut-être que le modèle Haskell peut être utile: http://www.haskell.org/bz/thdoc.htm http://www.haskell.org/haskellwiki/Template_Haskell – primodemus

+0

@primodemus: Avec TH, je pourrais créer des instances pour 'ArrConv' pour les tableaux de 10 dimensions maximum, et ils utiliseraient des tuples normaux pour les index, ce qui est une amélioration.Mais je pense que la limite est arbitraire et que le code va probablement être beaucoup moins lisible. – yairchu

Répondre

4

La raison pour laquelle les arguments supplémentaires à acBounds et acFlatten sont nécessaires est que les types a et i ne peuvent pas être récupérés respectivement de ListOfIndex i a -> (i, i) et ListOfIndex i a -> [a]. Une solution de contournement consiste à combiner les deux méthodes dans une méthode acArgs de type ListOfIndex i a -> ((i, i), a). Maintenant le seul problème est de l'utiliser dans l'instance de (Int, i) d'une manière qui empêche le typechecker de trop généraliser son type causant le même problème qu'auparavant (par exemple, nous ne pouvons pas simplement utiliser 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 
+0

@Reid Barton: Génial! Merci :) J'ai refactorisé votre réponse un peu (J'espère que cela ne vous dérange pas): Au lieu de passer 'acArgs' à une fonction le rend monomorphe, il ne l'utilise plus qu'au même endroit. – yairchu

+0

Ah, je vois. Agréable. –

0

Si vous voulez garder acBounds et acFlatten séparé, vous pouvez ajouter un type-level tag argument à elle, à savoir acBounds aurait le type acBounds :: Proxy a -> ListOfIndex i a -> (i, i). Cela supprime le besoin des arguments undefined, puisque vous pouvez simplement lui passer (Proxy :: SomeConcreteType); et acBounds n'a aucun moyen d'en extraire des informations de niveau de valeur utiles, car il est isomorphe (de manière non typée) au type d'unité.

Questions connexes