2010-01-11 14 views
5

Zrobiłem funkcję podobną do numpy's array. Konwertuje list do tablic, list list do 2D tablice itpRodziny typu Haskella i atrapy argumentów

To działa tak:

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,())) a nie (Int, Int) bo nie wiem o Zautomatyzowane sposób, aby zwiększyć długość krotka. (pytanie boczne: czy jest taki sposób?)

Kodowanie było niewygodne i musiałem zrobić "obejście" (przekazywanie fałszywych argumentów do funkcji), aby działało. Zastanawiam się, czy jest lepszy sposób.

Więc oto kod, przerwane ze szczegółami brzydkie obejścia:

{-# 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 "powinno" być :: ListOfIndex i a -> (i, i). I podobnie dla acFlatten. Każdy podany jest obojętne zmienną (undefined jest zawsze podana wartość), ponieważ w przeciwnym razie nie mogę zmusić go do kompilacji :(

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

Powyżej jest obojętne undefined argumentem przechodząc w pracy. To opowiada GHC która instancję ListOfIndex użyć.

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

Poniższa funkcja powinna być funkcja w instancji ArrConvacBounds i jest zadeklarowana poza tylko dlatego, że muszę korzystać ScopedTypeVariables i nie wiem, jak mogę to zrobić w funkcja w definicji instancji.

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

"Nie znam programistycznego sposobu zwiększenia długości krotki." Nie sądzę, że możesz. To doskonały przykład funkcji, której typ zależy od wartości. Byłoby to łatwe do zrobienia w niezmiennie wpisywanym języku, jak "Agda", ale niemożliwe w Haskell. Może mógłbyś użyć "GADTs" w jakiś sposób, by dać ci jakieś uzależniające zachowanie, ale poza tym nie wiem jak. –

+0

Może Szablon Haskell może być przydatny: http://www.haskell.org/bz/thdoc.htm http://www.haskell.org/haskellwiki/Template_Haskell – primodemus

+0

@primodemus: Z TH mógłbym zrobić przykłady dla 'ArrConv' w przypadku tablic o maksymalnie 10 wymiarach i będą używać normalnych krotek dla indeksów, co jest poprawą.Ale czułbym, że limit jest arbitralny, a kod prawdopodobnie będzie znacznie mniej czytelny. – yairchu

Odpowiedz

4

Powodem, że dodatkowe argumenty acBounds i acFlatten są konieczne jest, że rodzaje a i i nie może być odzyskany z ListOfIndex i a -> (i, i) i ListOfIndex i a -> [a] odpowiednio. Jedno obejście polega na połączeniu tych dwóch metod w jedną metodę acArgs typu ListOfIndex i a -> ((i, i), a). Teraz jedynym problemem jest użycie go w instancji (Int, i) w sposób, który zapobiega zbyt dużemu generalizowaniu typu, powodując ten sam problem, co poprzednio (na przykład, nie możemy po prostu użyć 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: Awesome! Thanks :) Odpowiedziałem trochę na twoją odpowiedź (mam nadzieję, że nie masz nic przeciwko): Zamiast podania 'acArgs' do funkcji sprawiają, że jest monomorficzna, teraz używa go tylko w jednym miejscu. – yairchu

+0

Ach, rozumiem. Miły. –

0

Jeśli chcesz zachować acBounds i acFlatten oddzielne, można dodać do niej type-level tag argument, tj acBounds musiałby typ acBounds :: Proxy a -> ListOfIndex i a -> (i, i). To usuwa potrzebę argumentów undefined, ponieważ można po prostu przekazać do niego (Proxy :: SomeConcreteType); i acBounds nie ma możliwości wyodrębnienia z niej użytecznej informacji o poziomie wartości, ponieważ jest izomorficzna (w trybie bez typu) do typu jednostki.