【问题标题】:Haskell: Creating Type Classes for ZippersHaskell:为拉链创建类型类
【发布时间】:2009-05-18 17:09:37
【问题描述】:

所以我一直在阅读一些有关 Haskell(以及其他函数式语言,我想)中的 Zipper 模式的信息,以遍历和修改数据结构,我认为这对我来说是一个磨练技能的好机会在 Haskell 中创建类型类,因为 该类可以提供一个通用的遍历接口供我编写代码,而与遍历的数据结构无关。

我想我可能需要两个类 - 一个用于根数据结构,一个用于创建的特殊数据结构 遍历第一个:

module Zipper where

class Zipper z where
  go'up :: z -> Maybe z
  go'down :: z -> Maybe z
  go'left :: z -> Maybe z
  go'right :: z -> Maybe z

class Zippable t where
  zipper :: (Zipper z) => t -> z
  get :: (Zipper z) => z -> t
  put :: (Zipper z) => z -> t -> z

但是当我尝试使用一些简单的数据结构(如列表)时:

-- store a path through a list, with preceding elements stored in reverse
data ListZipper a = ListZipper { preceding :: [a], following :: [a] }

instance Zipper (ListZipper a) where
  go'up ListZipper { preceding = [] } = Nothing
  go'up ListZipper { preceding = a:ps, following = fs } = 
      Just $ ListZipper { preceding = ps, following = a:fs }
  go'down ListZipper { following = [] } = Nothing
  go'down ListZipper { preceding = ps, following = a:fs } = 
      Just $ ListZipper { preceding = a:ps, following = fs }
  go'left _ = Nothing
  go'right _ = Nothing

instance Zippable ([a]) where
  zipper as = ListZipper { preceding = [], following = as }
  get = following
  put z as = z { following = as }

或者二叉树:

-- binary tree that only stores values at the leaves
data Tree a = Node { left'child :: Tree a, right'child :: Tree a } | Leaf a
-- store a path down a Tree, with branches not taken stored in reverse
data TreeZipper a = TreeZipper { branches :: [Either (Tree a) (Tree a)], subtree :: Tree a }

instance Zipper (TreeZipper a) where
  go'up TreeZipper { branches = [] } = Nothing
  go'up TreeZipper { branches = (Left l):bs, subtree = r } =  
      Just $ TreeZipper { branches = bs, subtree = Node { left'child = l, right'child = r } }
  go'up TreeZipper { branches = (Right r):bs, subtree = l } =  
      Just $ TreeZipper { branches = bs, subtree = Node { left'child = l, right'child = r } }
  go'down TreeZipper { subtree = Leaf a } = Nothing
  go'down TreeZipper { branches = bs, subtree = Node { left'child = l, right'child = r } } =
      Just $ TreeZipper { branches = (Right r):bs, subtree = l }
  go'left TreeZipper { branches = [] } = Nothing
  go'left TreeZipper { branches = (Right r):bs } = Nothing
  go'left TreeZipper { branches = (Left l):bs, subtree = r } =
      Just $ TreeZipper { branches = (Right r):bs, subtree = l }
  go'right TreeZipper { branches = [] } = Nothing
  go'right TreeZipper { branches = (Left l):bs } = Nothing
  go'right TreeZipper { branches = (Right r):bs, subtree = l } =
      Just $ TreeZipper { branches = (Left l):bs, subtree = r }

instance Zippable (Tree a) where
  zipper t = TreeZipper { branches = [], subtree = t }
  get TreeZipper { subtree = s } = s
  put z s = z { subtree = s }

我无法编译它,我的每个 Zippable 实例定义都会出现很多这样的错误:

拉链.hs:28:14: 无法匹配预期的类型“z” 针对推断类型“ListZipper a” `z' 是一个刚性类型变量,由 Zipper.hs:10:20 中“zipper”的类型签名 在表达式中:ListZipper {preceding = [], following = as} 在“拉链”的定义中: zipper as = ListZipper {preceding = [], following = as} 在方法“拉链”的定义中

所以我不确定从这里去哪里。我怀疑我的问题是我正在尝试绑定这两个实例 一起,当(Zipper z) => 声明只是希望z 是任何Zipper。

【问题讨论】:

  • 如何使用 zipper 作为状态变量添加 Monad 实例。然后交换两个项目,你说“x
  • 这就是我今晚所做的 :) gist.github.com/115203

标签: haskell design-patterns typeclass zipper


【解决方案1】:

您还可以使用类型同义词族来代替多参数类型类和函数依赖项。在这样的情况下,它们提供了一个更清晰、更易于理解的解决方案。在这种情况下,类和实例将变为:

class Zippable t where
  type ZipperType t :: *
  enter :: t -> ZipperType t
  focus :: ZipperType t -> t

instance Zippable [a] where
  type ZipperType [a] = ListZipper a
  enter = ...
  focus = ...

Fun with type functions 是对已经熟悉 Haskell 的人的类型同义词族的极好介绍。不久前,我还写了 an article 关于如何经常使用类型同义词系列而不是功能依赖关系。

希望这会有所帮助!

【讨论】:

  • 类型族是在 GHC 6.10.1 左右引入的?我还没有真正使用它们,但它们看起来很方便。
【解决方案2】:

(顺便说一句:你的go'up 命名方案是......很有创意。Haskell 风格通常是驼峰式。)

你在正确的轨道上。你写的和下面的一样。

{-# LANGUAGE RankNTypes #-}
instance Zippable [a] where
    zipper = ... :: forall z. (Zipper z) => [a] -> z
    get = ... :: forall z. (Zipper z) => z -> [a]
    set = ... :: forall z. (Zipper z) => z -> [a] -> z

(对于所有类型z,给定Zipper z,存在zipper :: [a] -> z。)

您正在尝试定义zipper = ... :: [a] -> ListZipper a,这显然过于严格了。

您的代码将通过以下最小更改进行类型检查:

{-# LANGUAGE MultiParamTypeClasses #-}
class (Zipper z) => Zippable z t where
    zipper :: t -> z
    get :: z -> t
    set :: z -> t -> z
instance Zippable (ListZipper a) [a] where
    ...
instance Zippable (TreeZipper a) (Tree a) where
    ...

见multi-parameter type classes。它是 Haskell 98 后的扩展,但 Haskell 实现广泛支持它。

【讨论】:

  • +1/已接受 - 非常感谢!我正在慢慢学习 Haskell,实际上还没有学习命名约定,但我会做到的。
  • OT:我什么时候应该在名字中使用撇号?
  • 我只看到它被用作“prime”。比如let x' = x + 1。它应该用于命名对旧值进行轻微修改的值。
  • 遵循数学中en.wikipedia.org/wiki/Prime_(symbol)的用法,撇号只用在名称的末尾,用于命名相关值。
  • 向 Zippable 添加函数依赖 t->z 可能是个好主意。否则,当您尝试使用这些类时,您会遇到类型歧义...(另请参阅haskell.org/haskellwiki/Functional_dependencies)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-05
  • 2015-02-03
  • 1970-01-01
  • 2011-02-22
  • 2016-07-19
  • 1970-01-01
相关资源
最近更新 更多