2012-05-11 23 views
7

digamos que tengo el siguiente códigoHaskell: Seguridad de tipos con diferentes valores booleanos lógicamente

type IsTall = Bool 
type IsAlive = Bool 

is_short_alive_person is_tall is_alive = (not is_tall) && is_alive 

Say, más adelante, tengo el siguiente

a :: IsAlive 
a = False 

b :: IsTall 
b = True 

y llame a la siguiente , consiguiendo los dos argumentos en torno a la manera equivocada:

is_short_alive_person a b 

Esto compila con éxito por desgracia, y al tiempo de ejecución de la gente alta muertos en su lugar se encuentran en lugar de personas cortas y vivas.

Me gustaría que no se compile el ejemplo anterior.

Mi primer intento fue:

newtype IsAlive = IsAlive Bool 
newtype IsTall = IsTall Bool 

Pero entonces no puede hacer algo así.

switch_height :: IsTall -> IsTall 
switch_height h = not h 

Como not no está definido en IsTall s, sólo Bool s.

Podría extraer explícitamente el Bool s todo el tiempo, pero eso en gran medida frustra el propósito.

Básicamente, quiero IsTall s interactuar con otros IsTall s, al igual que lo son Bool s, excepto que no van a interactuar con Bool s y s IsAlive sin una conversión explícita.

¿Cuál es la mejor manera de lograr esto.


p.s. Creo que he logrado esto con los números haciendo en GHC:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} 

newtype UserID = UserID Int deriving (Eq, Ord, Num) 
newtype GroupID = GroupID Int deriving (Eq, Ord, Num) 

(es decir, ID de usuario y de GroupID de no deberían interactuar)

pero me parece que no puede hacer esto con Bool s (que deriva de Bool no funciona). Ni siquiera estoy seguro de que lo anterior sea el mejor enfoque de todos modos.

+1

Los tipos deseados 'IsTall' y' IsAlive' son una idea terrible. Es una falsa generalización de la idea generalmente decente de usar tipos disjuntos para garantizar la seguridad del tipo. Compare esto con su 'UserID' y' GroupID'; en ese caso, tiene sentido tener tipos separados porque no tiene sentido pasar un 'UserId' donde se necesita un' GroupID', o agregar uno al otro (aunque probablemente ninguno debe implementar 'Num'). Sin embargo, tiene sentido probar si una persona es alta * y * viva, alta * o * viva, * no * alta * y * viva, etc. –

+0

sacundim: En gran medida cambié de opinión hasta este punto de ver. Terminé usando cosas como 'newtype Height = Tall | Corto' y luego haciendo 'x == Tall', etc. Un poco más de tipeo, pero pensé que hacía que el código fuera más legible y seguro. – Clinton

Respuesta

8

Se puede llegar de alguna manera a esto, el uso de newtype s y una clase si importa el Preludio que oculta las funciones booleanas que desea usar con sus valores IsTall y IsAlive. Se redefinirán las funciones booleanas como métodos en la clase, para lo cual se crearán instancias para los 3 tipos de Bool, IsTall y IsAlive. Si usa GeneralizedNewtypeDeriving, puede incluso obtener las instancias IsTall y IsAlive sin tener que escribir la regla repetitiva de envoltura/desempaquetado a mano.

He aquí un ejemplo de script en realidad probé en ghci:

{-# LANGUAGE GeneralizedNewtypeDeriving #-} 

import Prelude hiding ((&&), (||), not) 
import qualified Prelude 

class Boolish a where 
    (&&) :: a -> a -> a 
    (||) :: a -> a -> a 
    not :: a -> a 

instance Boolish Bool where 
    (&&) = (Prelude.&&) 
    (||) = (Prelude.||) 
    not = Prelude.not 

newtype IsTall = IsTall Bool 
    deriving (Eq, Ord, Show, Boolish) 

newtype IsAlive = IsAlive Bool 
    deriving (Eq, Ord, Show, Boolish) 

Ahora puede &&, || y not valores de cualquiera de los tres tipos, pero no juntos. Y son tipos distintos, por lo que las firmas de función ahora pueden restringir cuál de las 3 quieren aceptar.

Superior funciones de orden definidos en otros módulos tendrán ningún problema con esto, como en:

*Main> map not [IsTall True, IsTall False] 
[IsTall False,IsTall True] 

Pero no serán capaces de pasar una IsTall a cualquier otra función definida en otro lugar que espera un Bool, porque el otro módulo seguirá utilizando la versión Prelude de las funciones booleanas. Las construcciones de lenguaje como if ... then ... else ... seguirán siendo un problema también (aunque un comentario de hammar sobre la respuesta de Norman Ramsey dice que puedes arreglar esto con otra extensión de GHC). Probablemente agregue un método toBool a esa clase para ayudar a convertir de manera uniforme a los Bool s regulares para ayudar a mitigar dichos problemas.

+0

http://hackage.haskell.org/package/cond es útil si desea tomar este enfoque. –

8

Sus opciones son para definir los tipos de datos algebraicos como

data Height = Tall | Short 
data Wiggliness = Alive | Dead 

o definir nuevos operadores, por ejemplo, &&&, |||, complement y sobrecargarlos del tipo de su elección. Pero incluso con sobrecarga, no podrá usarlos con if.

No estoy seguro de que las operaciones booleanas en altura tengan sentido de todos modos. ¿Cómo justifica una conclusión de que "alto y corto es igual a corto" pero "alto o bajo es igual a alto"?

Le sugiero que busque diferentes nombres para sus conectivos, que luego puede sobrecargar.

P.S. Haskell siempre está obteniendo nuevas funciones, así que lo mejor que puedo decir es que si puedes sobrecargar if, no estoy al tanto.Decir sobre Haskell que "tal y tal no se puede hacer" siempre es peligroso ...

+3

Puede sobrecargar 'si' utilizando la extensión' RebindableSyntax' en las versiones recientes de GHC. [Ejemplo rápido] (https://gist.github.com/2657492). – hammar

+0

@Norman: Intenté la respuesta de Ben, que funcionó bastante bien, pero al final decidí que era más limpio definir los Enums como sugeriste. Me di cuenta de que podía hacer lo que hacía en gran medida sin operaciones lógicas. – Clinton

11

Si cambia su tipo de datos ligeramente, puede convertirlo en una instancia de Functor y luego puede usar fmap para hacer las operaciones en el booleana

import Control.Applicative 

newtype IsAliveBase a = IsAlive a 
newtype IsTallBase a = IsTall a 

type IsAlive = IsAliveBase Bool 
type IsTall = IsTallBase Bool 

instance Functor IsAliveBase where 
    fmap f (IsAlive b) = IsAlive (f b) 

instance Functor IsTallBase where 
    fmap f (IsTall b) = IsTall (f b) 

switch_height :: IsTall -> IsTall 
switch_height h = not <$> h -- or fmap not h 

- EDIT

para operaciones como & & que puede hacer que sea una instancia de Aplicativo

instance Applicative IsAliveBase where 
    pure = IsAlive 
    (IsAlive f) <*> (IsAlive x) = IsAlive (f x) 

y entonces usted puede hacer (& &) usando liftA2

ejemplo:

*Main> let h = IsAlive True 
*Main> liftA2 (&&) h h 
IsAlive True 

se puede leer más sobre esto en http://en.wikibooks.org/wiki/Haskell/Applicative_Functors

+0

¿Cómo hago h1 && h2? – Clinton

+0

Se actualizó la respuesta con un ejemplo para && – Phyx

Cuestiones relacionadas