data Foo = Bar1
         | Bar2 Foo Foo
         | Bar3 Foo
         | Bar4 Foo Foo Foo


现在,假设有人建立了一个Foo树,我想检查Foo值的参数是否有效。构造函数参数的规则是:


Bar2 Bar1 Foo
Bar3 (Bar2|Bar3|Bar4)
Bar4 Bar1 Bar1 (Bar1|Bar4)


我知道值的构造函数,只想检查立即参数,没有递归的方法。像这样:

bar2 Bar1 Bar1      = True
bar2 Bar1 (Bar2 {}) = True
bar2 Bar1 (Bar3 _)  = True
bar2 Bar1 (Bar4 {}) = True
bar2 _ _ = False


和例如对于Bar4同样:

bar4 Bar1 Bar1 Bar1      = True
bar4 Bar1 Bar1 (Bar4 {}) = True
bar4 _ _ _ = False


如何最简洁地表达这些条件?列出所有组合在某些情况下有点过多。据我所知,不存在用于模式匹配的“ OR”语法。

更新

我改编了Daniel的解决方案,并得出以下结论:

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


我喜欢的是,除了添加派生子句外,我不必更改数据类型,并且它是可读的。当然,缺点是这些是动态检查。就我而言,这很好,因为它仅用于类似断言的检查以发现编程错误。

最佳答案

有一个很好的静态检查解决方案;这是一个动态检查解决方案的建议。关键思想是避免使用模式匹配,而使用我们拥有的所有工具来编写紧凑的代码。这样做有两种选择。我将讨论两个。首先是为每个构造函数编写isBarX函数:

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)


另一个选择是编写一个函数,该函数返回一些数据,以告诉使用了哪个构造函数-例如,像data Constructor = CBar1 | CBar2 | CBar3 | CBar4这样的自定义类型,或者像我下面将要介绍的那样,像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]

10-06 01:56