Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate the list of Ints associated with an Enum type

Tags:

types

haskell

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?

like image 346
Chris Taylor Avatar asked Jul 29 '12 15:07

Chris Taylor


1 Answers

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])
like image 140
Vitus Avatar answered Oct 06 '22 00:10

Vitus