2
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”语法。

更新

我改编了丹尼尔的解决方案并得出了这个结论:

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

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

4

2 回答 2

5

我认为如何在类型系统级别检查它的唯一方法是将其拆分为几种数据类型。就像是:

data Foo = Foo1 Bar1 | Foo2 Bar2 | Foo3 Bar3 | Foo4 Bar4
data Bar1 = Bar1
data Bar2 = Bar2a Bar1 Foo
data Bar3 = Bar3a Bar2 | Bar3b Bar3 | Bar3c Bar4
data Bar4 = Bar4a Bar1 Bar1 Bar1 | Bar4b Bar1 Bar1 Bar4

您不能说要将某些类型变量限制为某些构造函数,因此您需要为此创建一个新的数据类型。

缺点是您将需要输入更多的构造函数/模式,但这至少可以通过一些辅助函数来解决。


编辑:也许另一种解决方案是使用幻像类型来标记使用 GADT 的构造函数:

{-# LANGUAGE GADTs #-}

data FBar1
data FBar2
data FBar3
data FBar4

data Foo a where
    Bar1 :: Foo FBar1
    Bar2 :: Foo FBar1 -> Foo b -> Foo FBar2
    Bar3a :: Foo FBar2 -> Foo FBar3
    Bar3b :: Foo FBar3 -> Foo FBar3
    Bar3c :: Foo FBar4 -> Foo FBar3
    Bar4a :: Foo FBar1 -> Foo FBar1 -> Foo FBar4
    Bar4b :: Foo FBar1 -> Foo FBar4 -> Foo FBar4

我不确定这个解决方案是否会产生比它解决的问题更多的问题。例如,不可能编写这样的函数:

construct :: Int -> FooAny X
construct 0 = Bar1
construct 1 = Bar2 Bar1 Bar1

因为X必须同时是FBar1FBar2。为此,您需要存在主义,例如将其包装为

data FooAny where
    FooAny :: Foo a -> FooAny

construct :: Int -> FooAny
construct 0 = FooAny $ Bar1
construct 1 = FooAny $ Bar2 Bar1 Bar1
于 2012-08-17T16:22:19.507 回答
2

发布了一个很好的静态检查解决方案;这是动态检查解决方案的建议。关键思想是回避模式匹配并使用我们拥有的所有工具来编写紧凑的代码。这样做有几个选择;我会讨论两个。首先是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]
于 2012-08-17T18:27:44.347 回答