如何使用Haskell的类型系统来强制正确性,同时仍然能够进行模式匹配?

时间:2010-10-08 22:37:17

标签: haskell pattern-matching typeclass parsec abstract-data-type

假设我有一个代表某种树结构的adt:

data Tree = ANode (Maybe Tree) (Maybe Tree) AValType
          | BNode (Maybe Tree) (Maybe Tree) BValType
          | CNode (Maybe Tree) (Maybe Tree) CValType

据我所知,没有办法对类型构造函数进行模式匹配(或者匹配函数本身不会有类型?)但是我仍然希望使用编译时类型系统来消除返回或解析Tree节点的错误“类型”。例如,可能是CNode只能是ANodes的父母。我可能有

parseANode :: Parser (Maybe Tree)

作为Parsec解析函数,它被用作我的CNode解析器的一部分:

parseCNode :: Parser (Maybe Tree)
parseCNode = try (
                  string "<CNode>" >>
                  parseANode >>= \maybeanodel ->
                  parseANode >>= \maybeanoder ->
                  parseCValType >>= \cval ->
                  string "</CNode>"
                  return (Just (CNode maybeanodel maybeanoder cval))
             ) <|> return Nothing

根据类型系统,parseANode可能最终返回Maybe CNode,Maybe BNode或Maybe ANode,但我真的想确保它只返回一个Maybe ANode。请注意,这不是我想要做的数据或运行时检查的模式值 - 我实际上只是想检查我为特定树模式编写的解析器的有效性。 IOW,我不是要检查解析数据的模式正确性,我真正想做的是检查我的解析器的模式正确性 - 我只是想确保我不要总有一天,parseANode会返回除了ANode值以外的东西。

我希望如果我匹配绑定变量中的值构造函数,那么类型推理会找出我的意思:

parseCNode :: Parser (Maybe Tree)
parseCNode = try (
                  string "<CNode>" >>
                  parseANode >>= \(Maybe (ANode left right avall)) ->
                  parseANode >>= \(Maybe (ANode left right avalr)) ->
                  parseCValType >>= \cval ->
                  string "</CNode>"
                  return (Just (CNode (Maybe (ANode left right avall)) (Maybe (ANode left right avalr)) cval))
             ) <|> return Nothing

但是这有很多问题,而parseANode不再可以自由地返回Nothing。并且它无论如何都不起作用 - 看起来绑定变量被视为模式匹配,当parseANode返回Nothing或Maybe BNode之类的时候,运行时会抱怨非详尽的模式匹配。

我可以沿着这些方向做点什么:

data ANode = ANode (Maybe BNode) (Maybe BNode) AValType
data BNode = BNode (Maybe CNode) (Maybe CNode) BValType
data CNode = CNode (Maybe ANode) (Maybe ANode) CValType

但是这种情况很糟糕,因为它假设约束适用于所有节点 - 我可能对这样做不感兴趣 - 事实上它可能只是CNode只能为父节点提供父节点。所以我想我可以做到这一点:

data AnyNode = AnyANode ANode | AnyBNode BNode | AnyCNode CNode

data ANode = ANode (Maybe AnyNode) (Maybe AnyNode) AValType
data BNode = BNode (Maybe AnyNode) (Maybe AnyNode) BValType
data CNode = CNode (Maybe ANode) (Maybe ANode) CValType

然后这使得与* Node的模式匹配变得更加困难 - 实际上它是不可能的,因为它们只是完全不同的类型。我可以在任何想要模式匹配的地方制作一个类型类我猜

class Node t where
    matchingFunc :: t -> Bool

instance Node ANode where
    matchingFunc (ANode left right val) = testA val

instance Node BNode where
    matchingFunc (BNode left right val) = val == refBVal

instance Node CNode where
    matchingFunc (CNode left right val) = doSomethingWithACValAndReturnABool val

无论如何,这似乎有些混乱。谁能想到更简洁的做法呢?

3 个答案:

答案 0 :(得分:4)

  

我仍然希望使用编译时类型系统来消除返回或解析错误的“类型”树节点的可能性

这听起来像是GADT的用例。

{-# LANGUAGE GADTs, EmptyDataDecls #-}
data ATag
data BTag
data CTag

data Tree t where
  ANode :: Maybe (Tree t) -> Maybe (Tree t) -> AValType -> Tree ATag
  BNode :: Maybe (Tree t) -> Maybe (Tree t) -> BValType -> Tree BTag
  CNode :: Maybe (Tree t) -> Maybe (Tree t) -> CValType -> Tree CTag

现在,您可以在不关心节点类型时使用Tree t,或者在使用Tree ATag时使用{{1}}。

答案 1 :(得分:4)

我不理解您对最终解决方案的反对意见。你仍然可以对AnyNode进行模式匹配,如下所示:

f (AnyANode (ANode x y z)) = ...

它有点冗长,但我认为它具有您想要的工程属性。

答案 2 :(得分:1)

keegan答案的扩展:编码红/黑树的正确属性是一种规范的例子。该线程的代码显示了GADT和嵌套数据类型解决方案:http://www.reddit.com/r/programming/comments/w1oz/how_are_gadts_useful_in_practical_programming/cw3i9