【问题标题】:Haskell Type Destructuring in GuardsGuards 中的 Haskell 类型解构
【发布时间】:2016-04-13 15:16:15
【问题描述】:

我正在玩 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 中,这似乎并没有将 r 绑定到保护分支的 RHS 范围内。我尝试查找 Pattern Guards,因为它们听起来与我可能想要做的相似,但我无法真正了解这里发生了什么。真的,我只想从 = 的 LHS 到 equals 的 RHS 绑定(取决于我们已经关闭的守卫的哪个分支)。

什么是惯用的 Haskell 方式来做我在这里尝试做的事情?

【问题讨论】:

    标签: haskell functional-programming pattern-matching


    【解决方案1】:

    可以如下实现:

    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 使用 | 就像 ML 在定义代数数据类型时一样,来分隔不同的构造函数,但不使用它来分隔 case 分支,而是遵循语法

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

    可以用缩进代替

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

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

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

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

    【讨论】:

    • 也许你应该明确提到 Haskell 和 ML 在一般意义上使用 | 来标记“案例”,但是 Haskell 将它用于布尔值守卫,而 ML 将它(除其他外)用于模式匹配。还值得一提的是,通常不鼓励使用非总字段选择器(除了用于制作棱镜的非导出选择器)。
    • @dfeuer 那么出于好奇,实现 PRQuadtree 类型的更惯用方法是什么?白色、黑色和灰色节点都是PRQuadtree 的类型,但对于插入、删除等具有不同的行为以及不同的关联数据。在 Java 中,我使用 PRQuadtree 接口和 White、Black 和 Gray 节点具体类实现了这种行为。由于所有内部数据都是私有的(并且仅在该节点中需要),因此它们的实现是否不同并不重要。
    • @KyleRush,一种选择是将产品总和分解为多种类型,因此每个产品都有自己的记录类型。
    【解决方案2】:

    你也可以写:

    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{} -> ....
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-02
      • 2012-03-09
      相关资源
      最近更新 更多