data Foo = Bar1
| Bar2 Foo Foo
| Bar3 Foo
| Bar4 Foo Foo Foo
Now, assume someone built a Foo
tree and I want to check if the arguments of a Foo value are valid. The rules on the constructor arguments are:
Bar2 Bar1 Foo
Bar3 (Bar2|Bar3|Bar4)
Bar4 Bar1 Bar1 (Bar1|Bar4)
I know the constructor of the value and only want to check the immediate arguments, nothing recursive. Like this:
bar2 Bar1 Bar1 = True
bar2 Bar1 (Bar2 {}) = True
bar2 Bar1 (Bar3 _) = True
bar2 Bar1 (Bar4 {}) = True
bar2 _ _ = False
And e.g. similarly for Bar4:
bar4 Bar1 Bar1 Bar1 = True
bar4 Bar1 Bar1 (Bar4 {}) = True
bar4 _ _ _ = False
How can I express these conditions most concisely? Listing all combinations is in some cases a bit too much. And to my knowledge an "OR"-syntax for pattern matching doesn't exist.
UPDATE
I adapted Daniel's solution and have come to this:
data Foo = Bar1
| Bar2 Foo Foo
| Bar3 Foo
| Bar4 Foo Foo Foo
deriving (Data, Typeable)
bar2 a b = a `isOf` [Bar1] && b `isOf` [Bar1,Bar2{},Bar3{},Bar4{}]
bar4 a b c = [a,b] `areOf` [Bar1] && c `isOf` [Bar1,Bar4{}]
isOf l r = toConstr l `elem` map toConstr r
areOf l r = all (`isOf` r) l
What I like about this is that I don't have to change my data type, except adding the deriving clause, and that it's readable. The disadvantage of course is that these are dynamic checks. In my case this is fine though as it's just for assert-like checks to discover programming errors.
There is a fine static-checking solution posted; here's a suggestion for a dynamic-checking solution. The key idea is to shy away from pattern-matching and use all the tools we have for writing compact code instead. There's a couple choices for doing this; I'll discuss two. The first is to write isBarX
functions for each constructor:
isBar1 (Bar1 {}) = True
isBar1 _ = False
-- ...
isBar4 (Bar4 {}) = True
isBar4 _ = False
valid (Bar1) = True
valid (Bar2 a b) = isBar1 a
valid (Bar3 a) = not (isBar1 a)
valid (Bar4 a b c) = isBar1 a && isBar1 b && (isBar1 c || isBar4 c)
Another option is to write a function that returns some data telling which constructor was used -- say, a custom type like data Constructor = CBar1 | CBar2 | CBar3 | CBar4
, or, as I'll do below, something a little hackier, like Int
.
constr (Bar1 {}) = 1
constr (Bar2 {}) = 2
constr (Bar3 {}) = 3
constr (Bar4 {}) = 4
valid (Bar1) = True
valid (Bar2 a b) = constr a == 1
valid (Bar3 a) = constr a /= 1
valid (Bar4 a b c) = constr a == 1 && constr b == 1 && constr c `elem` [1,4]