2011-12-14 7 views
5

Duplicar posibles:
How to create a type bounded within a certain rangevalores Restricción en constructores de tipo

tengo el tipo de datos:

data Expr = Num Int 
      | Expression Expr Operator Expr 

En el contexto del problema, los números que (Num Int) representará solo un dígito. ¿Hay alguna forma de garantizar esa restricción dentro de la declaración de tipo?

Por supuesto que podríamos definir una función para probar si el Expr es válido, pero sería bueno tener el sistema de tipo manejarlo.

Respuesta

12

Se puede utilizar un tipo de datos abstracto con un smart constructor:

newtype Digit = Digit { digitVal :: Int } 
    deriving (Eq, Ord, Show) 

mkDigit :: Int -> Maybe Digit 
mkDigit n 
    | n >= 0 && n < 10 = Just (Digit n) 
    | otherwise = Nothing 

Si pones esto en otro módulo y no exporta el constructor Digit, a continuación, el código de cliente no puede construir valores del tipo Digit fuera del rango [0,9], pero tiene que envolverlo y desenvolverlo manualmente para usarlo. Podría definir una instancia Num que haga aritmética modular, si eso fuera útil; eso también te permitiría usar literales numéricos para construir Dígitos. (Del mismo modo para Enum y Bounded.)

Sin embargo, esto no garantiza que nunca intenta crear un dígito inválido, sólo que nunca se hace . Si desea más seguridad, entonces la solución manual que ofrece Jan es mejor, a costa de ser menos conveniente. (Y si define una instancia Num para ese tipo de dígito, terminará igual de "inseguro", porque podría escribir 42 :: Digit gracias al soporte numérico literal que obtendría)

(Si usted no sabe lo que newtype es decir, que es básicamente data de tipos de datos con un solo campo, el estricto;. un envoltorio newtype alrededor T tendrá la misma representación en tiempo de ejecución como T es básicamente una optimización, por lo que puede pretender que dice data con el fin de entender esto.)

Editar: Para la solución más orientada a la teoría, 100%, consulte la sección de comentarios algo apretados de esta respuesta.

+0

Eso haría el trabajo, pero el sistema de tipos no es el que está manejando el problema. Eso es más de lo que estoy buscando. –

+2

Derecha: la garantía adicional aquí es que ningún valor de tipo 'Digit' puede ser inválido, en lugar de simplemente validar un' Expr' después de los hechos. Tenga en cuenta que incluso la solución de enumeración manual permite valores como 'error 'oops' ':: Digit'. Hay formas de manejar estas cosas de manera sólida y conveniente en sistemas de tipo avanzado, pero todavía no han llegado a Haskell, por lo que un compromiso como este es probablemente la mejor solución. (No es que hayan llegado a ningún otro lenguaje del "mundo real".) – ehird

+0

Si el sistema de tipos estuviera manejando el problema, no tendría que validar el 'Expr' porque no se compilaría en el primer lugar, ¿verdad? –

5

Dado que solo hay diez posibilidades, puede usar Enum para especificarlas todas.

data Digit = Zero | One | Two deriving (Enum, Show) 

Entonces habría que utilizar fromEnum tratarlos como números.

1 == fromEnum One 

Del mismo modo, el uso de toEnum se puede obtener una Digit de un número.

toEnum 2 :: Digit 

Podemos ir más allá e implementar Num.

data Digit = Zero | One | Two deriving (Enum, Show, Eq) 

instance Num Digit where 
    fromInteger x = toEnum (fromInteger x) :: Digit 
    x + y = toEnum $ fromEnum x + fromEnum y 
    x * y = toEnum $ fromEnum x * fromEnum y 
    abs = id 
    signum _ = 1 

Zero + 1 + One == Two 
Cuestiones relacionadas