2012-07-29 7 views
6

Tengo una función de utilidad que enumera todos los valores de un tipo que es a la vez enumerables y delimitada:Generar la lista de Entrs asociado con un tipo de enumeración

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

y un tipo de datos que implica la cartografía tipos enumerables a números enteros :

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

Dónde vals es la lista de números enteros que representan todos los posibles valores enumerables. Por ejemplo, si tuviera

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

continuación vals habría [0,1,2].

Quiero ser capaz de crear estos atributos programáticamente, con solo una función que asigna un a a un tipo enumerable, y un nombre. Algo como esto:

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

Esto no typecheck, porque no hay manera de conectar la llamada a enumerate con el b en la declaración de tipo. Así que pensé que podría hacer esto:

vs = map fromEnum $ enumerate :: [b] 

pero eso no compila bien - el compilador cambia el nombre que b-b1. He tratado de ser más inteligente, utilizando la extensión GADTs:

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

pero de nuevo, la c se cambia el nombre a c1.

no quiero incluir el tipo de b como parámetro en el tipo Attribute (principalmente porque quiero almacenar listas de atributos potencialmente con diferentes valores de b - Por eso test tiene por tipo a -> Int y vals tiene por tipo [Int])

¿Cómo puedo escribir este código para que haga lo que quiero que haga?

Respuesta

6

El problema con las variables de tipo es que solo están vinculadas en la firma de tipo. Cualquier uso de variables de tipo dentro de la definición se referirá a la nueva variable de tipo nuevo (aunque tenga exactamente el mismo nombre que en la firma de tipo).

Hay dos maneras de referirse a las variables de tipo desde la firma: ScopedTypeVariables extensión y asTypeOf.

Con ScopedTypeVariables una variable de tipo ligado explícitamente con forall también está disponible en la definición, por lo tanto:

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

Si podemos obtener una expresión:

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

La otra forma la función asTypeOf definido como implica del tipo [b] en el segundo parámetro, la unificación se asegurará de que el primer parámetro también tenga el tipo [b].Debido a que tenemos f :: a -> b y f undefined :: b, podemos escribir:

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 variables de tipo funciona perfecly, gracias! –

+0

Además, esta es la primera vez que veo 'undefined' utilizado para realizar una tarea útil. –

+0

@ChrisTaylor: Por supuesto, puede utilizar una especialización diferente de 'const', como' asTypeOf2 :: [b] -> (a -> b) -> [b] 'y luego puede escribir' enumerate \ 'asTypeOf2 \ 'f', pero probablemente no valga la pena el esfuerzo. – Vitus

Cuestiones relacionadas