tags:

views:

192

answers:

5

Lets say we have this type declaration:

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

and want to define a function over it which divides the data constructors in 2 sets. It would be nice to write something like that:

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

instead of matching on each constructor separately.

Is there any way to achieve this? I looked at uniplate but couldn't find a way to do it.

+1  A: 

Edit: If all constructors have the same type of fields, you could abuse 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

You could try

{-# 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)
KennyTM
I found the same solution. The problem is with the String literals. It will be better if we can match against `[A,B,C]` as in the example I gave.
Daniel Velkov
@djv: See update.
KennyTM
It's getting better, but what if I want it to work for constructors with different number fields?
Daniel Velkov
@djv: Then `[A,B,C,D,E,F]` does not have a valid type.
KennyTM
Hm, yes you are right. But there should be some workaround. Maybe I'll have to use Template Haskell in the end.
Daniel Velkov
@KennyTM: Probably it's possible to use tuple like `(a -> D a, (b -> b -> D b, ()))` (yesterday I saw that in LLVM bindings for indexing)
ony
A: 

It's a bit of a hack, but how about this, using Data.Data and a "placeholder" type?

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

Note that this avoids the issue of type signature/different constructor arity. There's probably a cleaner way to do something similar, as well.

camccann
You don't need `X`, just use `()` as a placeholder.
Daniel Velkov
@djv: I wanted an explicit placeholder, to distinguish it from other types. But yes, `()` or almost anything else would work just as well.
camccann
+1  A: 

I've tried to generalize answer of @KennyTM with:

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

But by some reason it doesn't work for constructor C

ony
+2  A: 

If you often need to match for the same set of constructors, a helper function could be the simplest solution. For example:

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

With such a helper function, the definition of g can be simplified like this:

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

Or, using the maybe function:

g = maybe 2 (\v -> 1) . getAbc
sth
This seems like the cleanest solution to me.
Matthieu M.
A: 

I wish that Haskell patterns would have a way of specifying the "OR" of two patterns, similar to | in OCaml:

(* ocaml code *)
let g x = match x with
            A v | B v | C v -> 1
          | C v | D v | E v -> 2
newacct
What will be if `A`, `B` and `C` will have different types? How `1` can work with `v` if it matched with three different constructors? What will be if `C` will take two values? How `2` will know that there is another name for second value matched in `C` constructor in contrary to `D` where only `v` is available?
ony
Well I put `v` in there just to show that you can get the value out of all three. But since `v` is not used, of course you could just do `A _ | B _ | C _` and it wouldn't matter if they had different types. And if `C` takes two values (in OCaml you can't take two values), you would just write `C _ _`. That way the type checker would be able to check that it is right.
newacct