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: 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])
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With