2010-07-18 12 views
9

digamos que tenemos esta declaración de tipo:juego varios constructores de tipos de datos a la vez

data D a = A a | B a | C a | D a | E a | F a 

y queremos definir una función sobre ella que divide los constructores de datos en 2 juegos. Sería bueno escribir algo así:

g x | x `is` [A,B,C] = 1 
    | x `is` [D,E,F] = 2 

en lugar de hacer coincidir cada constructor por separado.

¿Hay alguna manera de lograr esto? Miré uniplate pero no pude encontrar una manera de hacerlo.

Respuesta

4

Editar: Si todos los constructores tienen el mismo tipo de campos, se podría abusar Functor:

{-# LANGUAGE DeriveFunctor #-} 

data D a = A a | B a | C a | D a | E a | F a 
    deriving (Eq, Functor) 

isCons :: (Eq (f Int), Functor f) => f a -> (Int -> f Int) -> Bool 
isCons k s = fmap (const 42) k == s 42 

is :: (Eq (f Int), Functor f) => f a -> [Int -> f Int] -> Bool 
is k l = any (isCons k) l 

g :: D a -> Int 
g x | x `is` [A,B,C] = 1 
    | x `is` [D,E,F] = 2 

usted podría intentar

{-# LANGUAGE DeriveDataTypeable #-} 

import Data.Data 

data D a = A a | B a | C a | D a | E a | F a 
     deriving (Typeable, Data) 

g :: Data a => D a -> Int 
g x | y `elem` ["A","B","C"] = 1 
    | y `elem` ["D","E","F"] = 2 
    where y = showConstr (toConstr x) 
+0

Encontré la misma solución. El problema es con los literales de cadena. Será mejor si podemos hacer coincidir contra '[A, B, C]' como en el ejemplo que di. –

+0

@djv: ver la actualización. – kennytm

+0

Está mejorando, pero ¿y si quiero que funcione para constructores con campos numéricos diferentes? –

0

Es un poco de un truco, pero ¿cómo sobre esto, usando Data.Data y un tipo de "marcador de posición"?

{-# LANGUAGE DeriveDataTypeable #-} 

import Data.Data 

data X = X deriving (Show, Data, Typeable) 
data D a = A a | B a | C a | D a a | E a a | F a a 
    deriving (Show, Data, Typeable) 


matchCons :: (Data a) => D a -> [D X] -> Bool 
matchCons x ys = any ((== xc) . toConstr) ys 
    where xc = toConstr x 

g :: (Data a) => D a -> Int 
g x | matchCons x [A X, B X, C X] = 1 
    | matchCons x [D X X, E X X, F X X] = 2 

Tenga en cuenta que esto evita el problema de la firma de tipo/aria de constructor diferente. Probablemente haya una manera más limpia de hacer algo similar, también.

+0

No lo haces necesita 'X', simplemente usa'() 'como marcador de posición. –

+0

@djv: quería un marcador de posición explícito, para distinguirlo de otros tipos. Pero sí, '()' o casi cualquier otra cosa funcionaría igual de bien. –

2

he tratado de generalizar la respuesta de @KennyTM con:

data D a = A a | B a | C a a | D 
    deriving (Show, Eq, Functor) 

class AutoBind a where 
    bindSome :: forall b . (a -> b) -> b 

instance AutoBind Bool where bindSome f = f False 
instance Num a => AutoBind a where bindSome f = f 0 

class AutoConst a b | a -> b where {- bind until target type -} 
    bindAll :: a -> b 

instance AutoBind a => AutoConst (a -> b) b where bindAll = bindSome 
instance (AutoBind a, AutoConst b c) => AutoConst (a -> b) c where bindAll = bindAll . bindSome 

isCons :: (Eq (f a), AutoBind a, AutoConst b (f a), Functor f) => f a -> b -> Bool 
isCons x y = fmap (bindSome const) x == bindAll y 

Pero por alguna razón no funciona para el constructor C

5

Si a menudo necesario para que coincida con el mismo conjunto de constructores, una función auxiliar podría ser la solución más simple. Por ejemplo:

getAbc :: D a -> Maybe a 
getAbc (A v) = Just v 
getAbc (B v) = Just v 
getAbc (C v) = Just v 
getAbc _  = Nothing 

Con una función de este tipo de ayuda, la definición de g puede simplificarse así:

g x = g_ (getAbc x) 
    where 
    g_ (Just v) = 1 
    g_ Nothing = 2 

O, usando la función maybe:

g = maybe 2 (\v -> 1) . getAbc 
+0

Esto me parece la solución más limpia. –

0

Deseo que Los patrones de Haskell tendrían una forma de especificar el "OR" de dos patrones, similar a | en OCaml:

(* ocaml code *) 
let g x = match x with 
      A v | B v | C v -> 1 
      | C v | D v | E v -> 2 
+0

¿Qué sucederá si 'A',' B' y 'C' tendrán diferentes tipos? ¿Cómo '1 'puede funcionar con' v' si coincide con tres constructores diferentes? ¿Qué será si 'C' tomará dos valores? ¿Cómo '2' sabrá que hay otro nombre para el segundo valor emparejado en el constructor' C' en contra de 'D' donde solo' v' está disponible? – ony

+0

Bueno, puse 'v' allí para mostrar que puedes obtener el valor de los tres. Pero como 'v' no se usa, por supuesto podrías hacer' A _ | B _ | C _' y no importaría si tuvieran diferentes tipos. Y si 'C' toma dos valores (en OCaml no puede tomar dos valores), simplemente escribiría' C _ _'. De esa forma, el verificador de tipos podría verificar que sea correcto. – newacct

0

Tenía la misma pregunta. Mi solución sería usar una vista, aunque personalmente me gustaría algo que sea canónicamente equivalente semánticamente (en algunos de los códigos que estoy escribiendo, la conservación de la pereza es crítica, por lo que cualquier coincidencia innecesaria podría hacer que la técnica no se pueda utilizar).

{-# LANGUAGE ViewPatterns #-} 

data D a = A a | B a | C a | D a | E a | F a 
isABC (A v) = Just v 
isABC (B v) = Just v 
isABC (C v) = Just v 
isABC _ = Nothing 

f :: D Int -> Int 
f (isABC -> Just v) = v 
f (isABC -> Nothing) = 0 
Cuestiones relacionadas