2012-07-29 17 views
6

Mam funkcję użytkową, który wylicza wszystkie wartości typu, który jest zarówno przeliczalny i ograniczony:Generowanie listy Ints powiązany z typem Enum

enumerate :: (Enum a, Bounded a) => [a] 
enumerate = [minBound .. maxBound] 

i typ danych, który obejmuje mapowania przeliczalny typy do liczb całkowitych :

data Attribute a = Attribute { test :: a -> Int 
          , vals :: [Int] 
          , name :: String } 

Gdzie vals lista liczb całkowitych reprezentujących wszystkie możliwe wartości przeliczalny. Na przykład, gdybym miał

data Foo = Zero | One | Two deriving (Enum,Bounded) 

następnie vals byłoby [0,1,2].

Chcę móc tworzyć te atrybuty programowo, korzystając tylko z funkcji odwzorowującej numer a na typ przeliczalny i nazwę. Coś takiego:

attribute :: (Enum b, Bounded b) => (a -> b) -> String -> Attribute a 
attribute f str = Attribute (fromEnum . f) vs str 
    where 
    vs = map fromEnum enumerate 

nie typecheck, ponieważ nie ma możliwości podłączenia wezwanie do enumerate z b w podpisie typu. Więc pomyślałem, mogłem to zrobić:

vs = map fromEnum $ enumerate :: [b] 

ale nie kompiluje albo - kompilator zmienia nazwę że b do b1. Starałem się być mądrzejszy, używając rozszerzenia GADTs:

attribute :: (Enum b, Bounded b, b ~ c) => {- ... -} 
vs = map fromEnum $ enumerate :: (Enum c,Bounded c) => [c] 

Ale znowu, c zostaje zmieniona na c1.

Nie chcę aby to rodzaj b jako parametr w Attribute typu (głównie dlatego, że chcę, aby przechowywać listy atrybutów z potencjalnie różnymi wartościami b - Dlatego test ma typ a -> Int i vals ma typ [Int]).

Jak mogę napisać ten kod, aby zrobił to, co chcę?

Odpowiedz

6

Problem ze zmiennymi typu polega na tym, że są one związane tylko w podpisie typu. Wszelkie użycie zmiennych typu wewnątrz definicji odnosi się do nowej, świeżej zmiennej typu (nawet jeśli ma dokładnie taką samą nazwę jak w podpisie typu).

Istnieją dwa sposoby odnoszenia się do zmiennych typu od podpisu: ScopedTypeVariables rozszerzenie i asTypeOf.

Z ScopedTypeVariables typem zmiennej wyraźnie związany z forall dostępny jest również w definicji, a więc:

attribute :: forall a b. (Enum b, Bounded b) => 
      (a -> b) -> String -> Attribute a 
attribute f str = Attribute (fromEnum . f) vs str 
    where 
    vs = map fromEnum (enumerate :: [b]) 

Drugi sposób polega na funkcję asTypeOf zdefiniowany jako:

asTypeOf :: a -> a -> a 
asTypeOf = const 

Jeśli możemy uzyskać ekspresję Typu [b] do drugiego parametru, ujednolicenie upewni się, że pierwszy parametr ma również typ [b].Ponieważ mamy f :: a -> b i f undefined :: b, możemy napisać:

attribute :: (Enum b, Bounded b) => (a -> b) -> String -> Attribute a 
attribute f str = Attribute (fromEnum . f) vs str 
    where 
    vs = map fromEnum (enumerate `asTypeOf` [f undefined]) 
+0

scoped Rodzaj Zmienne działa perfecly, dzięki! –

+0

Po raz pierwszy widzę niezdefiniowane 'używane do wykonania użytecznego zadania. –

+0

@ChrisTaylor: Mógłbyś oczywiście użyć innej specjalizacji 'const', takiej jak' asTypeOf2 :: [b] -> (a -> b) -> [b] ', a następnie możesz napisać wyliczenie \' asTypeOf2 \ 'f', ale prawdopodobnie nie jest to warte wysiłku. – Vitus

Powiązane problemy