4

Example:

data A =
  A B D
  | Aa B C
  | Ag B X X
  | Ae B R Q
  | Ax X

getB a = case a of 
    (A b _)         -> b
    (Aa b _)        -> b
    (Ag b _ _)      -> b
    (Ae b _ _)      -> b
    (Ax _)          -> somethingElse

In Haskell, given a data type where many of the constructors have the same argument type, is there a better way to return this argument. Or is there a better way to write the case statement shown above to have less repetition?

idunnololz
  • 8,058
  • 5
  • 30
  • 46

6 Answers6

4

A feature called "or patterns", available in ML, helps a lot with this. Such a feature was requested for GHC five years ago, but no one appears to have taken on the task of specifying the details and actually implementing it. However, there is a package offering a way to do something like this with Template Haskell, as explained in Or-patterns in Haskell

Community
  • 1
  • 1
dfeuer
  • 48,079
  • 5
  • 63
  • 167
3

You can use a Prism and the (^?) operator from the lens package to make this a little easier:

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

-- ...

data A =
  A { _b :: B, _d :: D }
  | Aa { _b :: B, _c :: C }
  | Ag { _b :: B, _x1 :: X, _x2 :: X }
  | Ae { _b :: B, _r :: R, _q :: Q }
  | Ax { _x1 :: X }

makeLenses ''A

getB :: A -> B
getB a = case a ^? b of
  Just theB -> theB
  Nothing   -> somethingElse

The call to the Template Haskell function makeLenses takes care of all the boilerplate.

lens might be a bit of a heavy dependency if you're only using it for this, but it's something to consider (especially if you are already using lens/considering using lens).

David Young
  • 10,713
  • 2
  • 33
  • 47
  • Isn't it generally considered poor form to name fields of non-record types? Is it possible to make just the total things? – dfeuer Jan 31 '15 at 22:27
  • @dfeuer I'm not sure what you mean by a "non-record type", but if those fields are just accessed through the `lens` functions, everything should stay nice and total. You could enforce this by not exporting the underscored names. – David Young Jan 31 '15 at 22:43
3

You can use record syntax to hack get around this:

data A =
  A {fieldB :: B, fieldC :: C} |
  Aa {fieldB :: B, fieldX1 :: X, fieldX2 :: X} |
  Ag {fieldB :: B, fieldR :: R, fieldQ :: Q} |
  Ax {fieldX :: X}

getB a = case a of
  Ax -> somethingElse
  _  -> fieldB a

The key thing is to give the same name to all the fields of type B.

MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220
  • This looks just like David Young's answer, except without adding the `lens` layer. – dfeuer Feb 02 '15 at 12:48
  • 1
    My point being that you don't need Template Haskell plus a large, complex library. It's just record syntax. – MathematicalOrchid Feb 02 '15 at 12:54
  • Yes, that's a point. I guess I just think it would make sense to mention the relationship between the two answers, as they're based on the same concept. – dfeuer Feb 02 '15 at 12:56
  • I'm marking this as the answer since this is the actual method I'm using to deal with this issue, but there were a lot of good answers and I have learned a lot. So thanks for all the answers guys :) – idunnololz Feb 04 '15 at 23:10
2

Pattern matching at the function level will help with readability but since these are all differents constructors there is no way to pattern match more than one of them at a time (that I know of).

getB (A b _)    = b
getB (Aa b _)   = b
getB (Ag b _ _) = b
getB (Ae b _ _) = b
getB (Ax _)     = somethingElse
gxtaillon
  • 1,016
  • 1
  • 19
  • 33
  • 3
    This. There is no fundamental correlation between the `b` in each of these constructors; they're just all coincidentally labeled the same thing. – Kenogu Labz Jan 31 '15 at 01:21
1

If A has a Data instance, you can write

import Data.Data
mgetB :: A -> Maybe B
mgetB = gmapQi 0 cast

and then define getB in terms of that function

aavogt
  • 1,308
  • 6
  • 14
1

At some point you have to say how your Bs are contained inside A so you many as well do it once and for all in a general way.

bOrX a = case a of
    (A b _)         -> B' b
    (Aa b _)        -> B' b
    (Ag b _ _)      -> B' b
    (Ae b _ _)      -> B' b
    (Ax x)          -> X' x

Subsequently you can match all Bs at once with very little code.

getB a = case bOrX a of
  B' b -> b
  X' _ -> somethingElse

anotherFunctionWithBandX a = case bOrX a of
  B' b -> f b
  X' x -> g x
Tom Ellis
  • 9,224
  • 1
  • 29
  • 54