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?

  1. 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: ScopedTypeVariables extension and asTypeOf.

    With ScopedTypeVariables a type variable explicitly bound with forall is 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 asTypeOf defined as:

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

    If 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 have f :: a -> b and f 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.