2012-07-29 13 views
6

ich eine Nutzenfunktion, die alle Werte eines Typs aufzählt, die sowohl zählbare und beschränkt:die Liste der Ints mit einer Enum-Typ zugeordnet

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

und ein Datentyp, Mapping enumerable Typen auf ganze Zahlen beinhaltet :

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

Wo vals ist die Liste der ganzen Zahlen alle möglichen zählbare Werte darstellen. Zum Beispiel hatte, wenn ich

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

dann vals[0,1,2] wäre.

Ich möchte in der Lage sein, diese Attribute programmatisch zu erstellen, nur mit einer Funktion, die eine a auf einen aufzählbaren Typ und einen Namen abbildet. Etwas wie folgt aus:

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

Diese typecheck nicht, denn es gibt keine Möglichkeit, den Anruf zu enumerate mit dem b in der Art Signatur zu verbinden. Also dachte ich, ich könnte dies tun:

vs = map fromEnum $ enumerate :: [b] 

aber das bedeutet nicht kompiliert entweder - die Compiler umbenennt, dass b-b1. Ich habe versucht, schlauer zu sein, die GADTs Erweiterung mit:

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

aber auch hier ist die c-c1 umbenannt.

Ich will nicht die Art von b als Parameter in dem Attribute Typ enthält (vor allem, weil ich Listen von Attributen mit potenziell unterschiedlichen Werten von b speichern will - das ist, warum testa -> Int hat geben und vals hat [Int] Typen).

Wie kann ich diesen Code schreiben, damit er das tut, was ich möchte?

Antwort

6

Das Problem mit Typvariablen ist, dass sie nur in der Typ-Signatur gebunden sind. Jede Verwendung von Typvariablen in der Definition bezieht sich auf eine neue, neue Typvariable (obwohl sie exakt denselben Namen wie in der Typensignatur hat).

Es gibt zwei Möglichkeiten, auf Typvariablen aus der Signatur zu verweisen: ScopedTypeVariables Erweiterung und asTypeOf.

Mit ScopedTypeVariables eine Variable vom Typ explizit mit forall gebunden ist auch in Definition verfügbar, also:

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]) 

Der andere Weg-Funktion beinhaltet asTypeOf wie folgt definiert:

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

Wenn wir einen Ausdruck bekommen vom Typ [b] in den zweiten Parameter, vereinheitlicht wird sicherstellen, dass der erste Parameter auch den Typ [b] hat.Weil wir f :: a -> b und f undefined :: b haben, können wir schreiben:

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 Typ Variablen arbeitet perfecly, danke! –

+0

Dies ist auch das erste Mal, dass ich "undefined" gesehen habe, um eine nützliche Aufgabe auszuführen. –

+0

@ChrisTaylor: Sie könnten natürlich eine andere Spezialisierung von 'const' verwenden, wie zum Beispiel' asTypeOf2 :: [b] -> (a -> b) -> [b] 'und dann können Sie' enumerate \ 'asTypeOf2 schreiben \ 'f', aber es ist wahrscheinlich nicht die Mühe wert. – Vitus

Verwandte Themen