tags:

views:

167

answers:

6

Consider the following:

data A = A1 | A2 Integer
x = [A1, A2 999]
elem A1 x == True

Is there a way to do the following test?

elem (A2 _) x == True
+8  A: 

No, but you could rephrase that using any.

hasA2 :: [A] -> Bool
hasA2 = any $ \x -> case x of { A2 _ -> True ; _ -> False }
Dietrich Epp
+4  A: 

No, for two reasons:

  1. No syntactic support for expressions of this sort.

  2. elem requiring a concrete first argument to search for in the list passed in as the second argument (so you can't do it with some minor fix to the syntax alone).

You can use Data.List.find instead:

import Data.List
isA2 A1     = False
isA2 (A2 _) = True
find isA2 [A1, A2 999]
-- => Just (A2 999)

Update: Well, Dietrich beat me to it, but I'll leave this answer here for the alternative solution (plus the explanation on top, FWIW).

Michał Marczyk
+2  A: 

A solution using Template Haskell:

{-# LANGUAGE TemplateHaskell #-}
import Data.ADT.Getters
import Data.Maybe

data A = A1 | A2 Integer
$(mkADTGetters ''A)

hasA2 = any (isJust . gA2)

Note: Data.ADT.Getters is in the "peakachu" hackage package, temporarily. This functionality will be added to "derive" and then removed from "peakachu".

yairchu
A: 
instance Eq A where
    A1   == A1   = True
    A2 _ == A2 _ = True
    _    == _    = False

elem (A2 undefined) x == True

Of course, this has effects beyond what you've asked for.

ephemient
In my case I think ([z | (A2 z) <- x] /= []) is the easiest since I have to use it only once but I believe defining an instance of A is probably the most proper alternative. Thanks, ephemient.
me2
@me2: Do not use `/= []`; instead, use `not . null`. See http://www.haskell.org/haskellwiki/Haskell_programming_tips#Don.27t_ask_for_the_length_of_a_list_when_you_don.27t_need_it for rationale.
ephemient
+2  A: 

To expand on Dietrich's answer:

If you simply want to match on a constructor name, disregarding all fields, you can use this pattern: A2 {}.

hasA2 :: [A] -> Bool
hasA2 = any $ \x -> case x of { A2 {} -> True; _ -> False }

This function will continue to work if you later decide to add another field to the A2 constructor.


And, if you're using ephemient Eq instance, you could also call it like so:

elem (A2 {})

Again, disregarding all fields (initializing them to bottom).

Tom Lokhorst
+1  A: 

Yes, this is doable! And if you don't like template haskell (used in another answer) then you can use either the "DrIFT" or "derive" tools.

{-! global : is !-}
data Foo = A1 | A2 Int deriving (Show, Eq, Ord)

hasA2 :: [Foo] -> Bool
hasA2 = any isA2

That is the code you type and as part of the compiler pipleine you run the source through DrIFT which generates the is* functions for all data types in the module:

{-* Generated by DrIFT : Look, but Don't Touch. *-}
isA1 (A1) = True
isA1 _ = False
isA2 (A2 _) = True
isA2 _ = False

Alternatively you can use "derive". Use the above code, remove the {-! ... !-} directive and insert:

{-!
deriving instance Is Foo
!-}

or you could save some typing and modify the original code to read:

data Foo = A1 | A2 Int deriving (Show, Eq, Ord {-! Is !-})

And "derive" will generate:

isA1 :: Foo -> Bool
isA1 (A1{}) = True
isA1 _ = False

isA2 :: Foo -> Bool
isA2 (A2{}) = True
isA2 _ = False

derive lives on Hackage while DrIFT can be found elsewhere.

TomMD