Haskell在卫兵中的类型解构

时间:2016-01-08 20:28:59

标签: haskell functional-programming pattern-matching

我在Haskell玩弄玩具项目。我之前实现了一些我用其他语言构建的数据结构,以熟悉它们是如何在Haskell中构建的。这不是我的第一个函数式语言,我已经在OCaml中构建了一些像Scheme解释器这样的项目,但我认为我的OCaml经验正在着色我如何处理这个问题。它'不是非常重要,但可能对上下文有用,要知道我实施的数据结构是PR-Quadtree。

我想要做的是匹配和解构一个守卫内部的类型,一个与OCaml的匹配声明。

data Waypoint = WayPoint {
    lat :: Float,
    lon :: Float,
    radius :: Float,
    speed :: Float,
    accel :: Float
} deriving (Show)

data Region = Region {
    x :: Float,
    y :: Float,
    width :: Float
} deriving (Show)

data PRQuadtree = WhiteNode Region
    | BlackNode Region Waypoint 
    | GreyNode { 
        topLeft :: PRQuadtree,
        topRight :: PRQuadtree,
        botLeft :: PRQuadtree,
        botRight :: PRQuadtree,
        region :: Region
    } deriving (Show)

getRegion node 
    | BlackNode(r, _) = r
    | WhiteNode(r) = r
    | GreyNode = region node 

getRegion函数是我遇到问题的函数。如果我试图做的事情不清楚:我想简单地提取参数的一个元素,但这取决于参数的代数数据类型的哪个成员。在OCaml中,我可以这样做:

let getRegion node = match node with
    | BlackNode(r, _) = r
    | WhiteNode(r) = r
    | GreyNode = region(node)

(或者类似的东西,我的OCaml现在有点生疏了。)

然而,在Haskell中,这似乎不会在保护分支的RHS范围内绑定r。我试图查找Pattern Guards,因为它们听起来和我想做的一样,但我真的不知道这里发生了什么。真的,我只是希望得到从LHS的LHS到等于的RHS的绑定(取决于我们已经下降的后卫的哪个分支)。

什么是惯用的Haskell做我想做的事情?

2 个答案:

答案 0 :(得分:6)

可以通过以下方式实现:

getRegion :: PRQuadtree -> Region
getRegion (BlackNode r _) = r
getRegion (WhiteNode r) = r
getRegion GreyNode{region=r} = r

或甚至

getRegion :: PRQuadtree -> Region
getRegion x = case x of
  BlackNode r _ -> r
  WhiteNode r -> r
  GreyNode{} -> region x

在Haskell中,预先设置类型签名是非常惯用的。

另一种选择是将region字段扩展到其他情况:

data PRQuadtree = WhiteNode { region :: Region }
    | BlackNode { region :: Region , waypoint :: Waypoint }
    | GreyNode { 
        topLeft :: PRQuadtree,
        topRight :: PRQuadtree,
        botLeft :: PRQuadtree,
        botRight :: PRQuadtree,
        region :: Region
    } deriving (Show)

现在,region将适用于所有PRQuadtree值。

Haskell在定义代数数据类型时使用|来分隔不同的构造函数,但不使用它来分隔case分支,而是使用语法

case .. of { pat1 -> e1 ; pat2 -> e2 ; ... }

可以用缩进替换

case .. of
   pat1 -> e1
   pat2 -> e2
   ...

另请注意,不鼓励使用部分字段选择器:

data A = A1 { foo :: Int } | A2

上面,foo A2类型检查但崩溃。另一方面,当所有构造函数中都存在字段时,我们不会面临这样的风险。

答案 1 :(得分:3)

你也可以写:

getRegion x 
    | BlackNode y <- x -> ....
    | Greynode{}  <- x -> ....

但在这个简单的案例中它是非常单一的。

但是,在更复杂的程序中,这种防护模式匹配非常有用。您可以使用多个方程式或大小写来区分一般情况,如@chi所示。但是,您可以检测特殊情况,例如以下组成的示例:

getRegion x = case x of
    BlackNode{region} 
        | [(0,_)] <- filter (inRegion region) interestingPoints
            -> -- region encloses exactly 1 interesting point on x axis
               ....
        | otherwise = ....
        where
           interestingPoints = .....
           inRegion :: Region -> Point -> Bool
    GreyNode{} -> ....