2010-07-18 5 views
9

Lets sagen, dass wir diese Art Erklärung haben:Passende mehrere Daten Typkonstruktoren sofort

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

und möchte eine Funktion über sie definieren, welche die Daten Konstrukteuren in 2 Sets unterteilt. Es wäre schön, etwas in der Art zu schreiben:

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

anstatt auf jeden Konstruktor separat zu vergleichen.

Gibt es eine Möglichkeit, dies zu erreichen? Ich schaute auf uniplate, konnte aber keinen Weg finden, es zu tun.

Antwort

4

Edit: Wenn alle Konstrukteure die gleiche Art von Feldern haben, könnten Sie Functor missbrauchen:

{-# 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 

Sie könnten versuchen,

{-# 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

Ich fand die gleiche Lösung. Das Problem liegt bei den String-Literalen. Es wird besser sein, wenn wir gegen '[A, B, C] 'wie in dem Beispiel, das ich gab, passen können. –

+0

@ djv: Siehe Aktualisierung. – kennytm

+0

Es wird besser, aber was, wenn ich möchte, dass es für Konstruktoren mit verschiedenen Zahlenfeldern funktioniert? –

0

Es ist ein bisschen wie ein Hack, aber wie etwa mit Data.Data und einem "Platzhalter" -Typ?

{-# 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 

Beachten Sie, dass dies das Problem der Typ-Signatur/anderer Konstruktor Arity vermeidet. Es gibt wahrscheinlich einen saubereren Weg, etwas Ähnliches zu tun.

+0

Sie nicht brauche 'X', verwende einfach'() 'als Platzhalter. –

+0

@ djv: Ich wollte einen expliziten Platzhalter, um ihn von anderen Typen zu unterscheiden. Aber ja, '()' oder fast alles andere würde genauso gut funktionieren. –

2

Ich habe versucht, mit Antwort von @KennyTM zu verallgemeinern:

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 

Aber aus irgendeinem Grund funktioniert es nicht für Konstruktor C

5

Wenn Sie oft passen müssen für den gleichen Satz von Konstruktoren könnte eine Hilfsfunktion die einfachste Lösung sein. Zum Beispiel:

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

Oder mit der maybe Funktion:

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

Mit einer solchen Hilfsfunktion, die Definition von g kann so vereinfacht werden

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

Das scheint mir die sauberste Lösung zu sein. –

0

Ich wünsche, dass Haskell-Muster hätten eine Möglichkeit, das "ODER" zweier Muster zu spezifizieren, ähnlich | in OCaml:

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

Was ist, wenn 'A',' B' und 'C' unterschiedliche Typen haben? Wie kann '1' mit' v' arbeiten, wenn es mit drei verschiedenen Konstruktoren übereinstimmt? Was ist, wenn "C" zwei Werte annehmen wird? Wie "2" wird wissen, dass es einen anderen Namen für den zweiten Wert gibt, der in "C" -Konstruktor übereinstimmt, im Gegensatz zu "D", wo nur "v" verfügbar ist? – ony

+0

Nun, ich habe 'v' drin, nur um zu zeigen, dass man den Wert aus allen dreien herausholen kann. Aber da 'v' nicht benutzt wird, könntest du natürlich' A _ | B _ | C 'und es wäre egal, wenn sie verschiedene Typen hätten. Und wenn "C" zwei Werte annimmt (in OCaml können Sie nicht zwei Werte annehmen), würden Sie einfach "C _ _" schreiben. Auf diese Weise könnte der Typüberprüfer prüfen, ob er richtig ist. – newacct

0

Ich hatte die gleiche Frage. Meine Lösung wäre, eine Ansicht zu verwenden, obwohl ich persönlich etwas kanonisch semantisch Äquivalentes bevorzugen würde (in einigen der Codes, die ich schreibe, ist die Faulheitserhaltung entscheidend, so dass zusätzliche unnötige Musterübereinstimmungen die Technik unbrauchbar machen könnten).

{-# 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