15

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?

orome
  • 45,163
  • 57
  • 202
  • 418
  • While there is no such thing available in the language (except , you might consider using [generics](https://wiki.haskell.org/Generics) or [Template Haskell](https://wiki.haskell.org/Template_Haskell) to generate such code. Would it be reasonable for your use case? – Petr Aug 22 '15 at 18:06

2 Answers2

21

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
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • I guess the choice of approaches (the one I was using vs. your second one, at least) depends on how many "tagged" one there are. In practice, I just have a few (and there are more constructors with slightly different forms) and I think the approach I have may be cleanest. Is there a *conceptual* reason to choose one of these three approaches over another? – orome Aug 22 '15 at 16:47
  • 1
    @raxacoricofallapatorius Which one makes sense depends greatly on what you want the type to mean -- just like you might write `2*1 + 2*2 + 2*3` in some cases and `2 * (1 + 2 + 3)` in others to emphasize different reasons for being interested in the number `12`. – Daniel Wagner Aug 22 '15 at 17:32
  • 4
    @raxacoricofallapatorius, if the strings represent the same thing in each case, the tag approach makes sense. If the strings are unrelated, you probably want three constructors. The fact that you want to do the same thing to the strings regardless suggests, but does not imply, that the tagged approach makes sense. – dfeuer Aug 22 '15 at 19:19
  • I didn't understand this first solution until I saw a complete example of record pattern matching: https://stackoverflow.com/a/38052886/3216883 – nevrome Oct 03 '21 at 19:32
5

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 Strings 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 Strings 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).

Petr
  • 62,528
  • 13
  • 153
  • 317