Reputation: 48456
I have
data Foo = X (String, Int) | A String | B String | C String | D String -- ...
and have defined
f (X (s, _)) =s
f (A s) = s
f (B s) = s
f (C s) = s
f (D s) = s
-- ...
but would prefer to be able to write something like
f (X (s, _)) =s
f (_ s) = s
But there seems to be no way to do this (I get a "parse error" associated with the _
).
Is there a way to match a data constructor wildcard in Haskell?
Upvotes: 15
Views: 1834
Reputation: 152707
Nope. But you can write this:
data Foo
= X { name :: String, age :: Int }
| A { name :: String }
| B { name :: String }
| C { name :: String }
| D { name :: String }
and then have name :: Foo -> String
. You could also consider this:
data Tag = A | B | C | D | X Int
data Foo = Tagged Tag String
f (Tagged _ s) = s
Upvotes: 21
Reputation: 63359
In addition to @DanielWagner's answer, an alternative is to use Scrap your boilerplate (AKA "SYB"). It allows you to find the first subterm of a given type. So you could define
{-# LANGUAGE DeriveDataTypeable #-}
import Control.Monad
import Data.Data
import Data.Generics.Schemes
import Data.Typeable
data Foo = X (String, Int) | A String | B String | C String | D String
deriving (Show, Eq, Ord, Data, Typeable)
fooString :: Foo -> Maybe String
fooString = msum . gmapQ cast
and fooString
will return the first String
argument of your constructors. Function cast
filters out String
s and gmapQ
gets the filtered values for all immediate subterms.
However, this won't return the String
from X
, because X
has no immediate String
subterm, it has only one subterm of type (String, Int)
. In order to get the first String
anywhere in the term hierarchy, you could use everywhere
:
fooString' :: Foo -> Maybe String
fooString' = everything mplus cast
Note that this approach is slightly fragile: It includes simply all String
s it finds, which might not be always what you want, in particular, if you later extend your data type (or some data type that it references).
Upvotes: 5