2010-07-18 6 views
9

consente di dire che abbiamo questo tipo di dichiarazione:di corrispondenza molteplici tipi di dati in una sola volta i costruttori

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

e vogliamo definire una funzione su di esso che divide i costruttori di dati in 2 set. Sarebbe bello scrivere qualcosa di simile:

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

invece di corrispondenza su ogni costruttore separatamente.

Esiste un modo per raggiungere questo obiettivo? Ho guardato uniplate ma non ho trovato il modo di farlo.

risposta

4

Edit: Se tutti i costruttori hanno lo stesso tipo di campi, si potrebbe abusare 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 

Si potrebbe provare

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

Ho trovato la stessa soluzione. Il problema è con i letterali String. Sarà meglio se potremo confrontarci con '[A, B, C]' come nell'esempio che ho dato. –

+0

@djv: vedere l'aggiornamento. – kennytm

+0

Sta migliorando, ma cosa succede se voglio che funzioni per costruttori con campi numerici diversi? –

0

E 'un po' di un hack, ma come su questo, usando Data.Data e un tipo "segnaposto"?

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

Si noti che ciò evita il problema della firma del tipo/diversa struttura del costruttore. Probabilmente c'è anche un modo più pulito per fare qualcosa di simile.

+0

Non si hai bisogno di 'X', usa'() 'come segnaposto. –

+0

@djv: volevo un segnaposto esplicito, per distinguerlo da altri tipi. Ma sì, '()' o quasi qualsiasi altra cosa funzionerebbe altrettanto bene. –

2

Ho cercato di generalizzare risposta di @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 

Ma da qualche motivo non funziona per il costruttore C

5

Se spesso è necessario corrispondere per lo stesso insieme di costruttori, una funzione di supporto potrebbe essere la soluzione più semplice. Per esempio:

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

Con una tale funzione di supporto, la definizione di g può essere semplificata in questo modo:

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

Oppure, usando la funzione maybe:

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

Questa mi sembra la soluzione più pulita. –

0

mi auguro che I pattern Haskell avrebbero un modo per specificare "OR" di due pattern, simili a | 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

Cosa succederà se 'A',' B' e 'C' avranno tipi diversi? Come '1' può funzionare con' v' se corrisponde a tre diversi costruttori? Cosa succederà se 'C' prenderà due valori? Come '2' saprà che esiste un altro nome per il secondo valore abbinato nel costruttore' C' al contrario di 'D' dove solo' v' è disponibile? – ony

+0

Bene, metto 'v' lì solo per mostrare che è possibile ottenere il valore di tutti e tre. Ma dal momento che 'v' non è usato, ovviamente puoi semplicemente fare' A _ | B _ | C _' e non avrebbe importanza se avessero tipi diversi. E se 'C' prende due valori (in OCaml non puoi prendere due valori), devi solo scrivere' C _ _'. In questo modo il controllore del tipo sarebbe in grado di verificare che sia giusto. – newacct

0

Ho avuto la stessa domanda. La mia soluzione sarebbe quella di usare una vista, anche se personalmente mi piacerebbe qualcosa che è più equivalente canonicamente semanticamente (in alcuni dei codici che sto scrivendo, la pigrizia è fondamentale, quindi qualsiasi schema aggiuntivo non necessario potrebbe rendere la tecnica inutilizzabile).

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