Generate the list of Ints associated with an Enum type-Collection of common programming errors
I have a utility function that enumerates all values of a type that is both enumerable and bounded:
enumerate :: (Enum a, Bounded a) => [a]
enumerate = [minBound .. maxBound]
and a data type that involves mapping enumerable types to integers:
data Attribute a = Attribute { test :: a -> Int
, vals :: [Int]
, name :: String }
Where vals is the list of integers representing all possible enumerable values. For example, if I had
data Foo = Zero | One | Two deriving (Enum,Bounded)
then vals would be [0,1,2].
I want to be able to create these attributes programatically, just given a function that maps an a to an enumerable type, and a name. Something like this:
attribute :: (Enum b, Bounded b) => (a -> b) -> String -> Attribute a
attribute f str = Attribute (fromEnum . f) vs str
where
vs = map fromEnum enumerate
This doesn’t typecheck, because there’s no way of connecting the call to enumerate with the b in the type signature. So I thought I could do this:
vs = map fromEnum $ enumerate :: [b]
but that doesn’t compile either – the compiler renames that b to b1. I tried to be smarter, using the GADTs extension:
attribute :: (Enum b, Bounded b, b ~ c) => {- ... -}
vs = map fromEnum $ enumerate :: (Enum c,Bounded c) => [c]
but again, the c is renamed to c1.
I don’t want to include the type of b as a parameter in the Attribute type (mainly because I want to store lists of attributes with potentially different values of b – that’s why test has type a -> Int and vals has type [Int]).
How can I write this code so that it does what I want it to do?
-
The problem with type variables is that they are only bound in the type signature. Any use of type variables inside definition will refer to new, fresh type variable (even though it has the exact same name as in the type signature).
There are two ways to refer to type variables from signature:
ScopedTypeVariablesextension andasTypeOf.With
ScopedTypeVariablesa type variable explicitly bound withforallis also available in definition, thus: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])The other way involves function
asTypeOfdefined as:asTypeOf :: a -> a -> a asTypeOf = constIf we can get an expression of type
[b]into the second parameter, unification will make sure that first parameter also has type[b]. Because we havef :: a -> bandf undefined :: b, we can write: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])
Originally posted 2013-11-09 19:01:36.