Haskell:为拉链创建类型类
所以我一直在阅读一些关于 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
实例定义:
Zipper.hs:28:14: Couldn't match expected type `z' against inferred type `ListZipper a' `z' is a rigid type variable bound by the type signature for `zipper' at Zipper.hs:10:20 In the expression: ListZipper {preceding = [], following = as} In the definition of `zipper': zipper as = ListZipper {preceding = [], following = as} In the definition for method `zipper'
所以我不知道从这里去哪里。 我怀疑我的问题是我试图绑定这两个实例 一起,当 (Zipper z) =>
声明只是希望 z
为任何 Zipper
时。
So I've been reading a bit about the Zipper pattern in Haskell (and other functional languages, I suppose) to traverse and modify a data structure, and I thought that this would be a good chance for me to hone my skills at creating type classes in Haskell, since
the class could present a common traversal interface for me to write code to, independent of the data structure traversed.
I thought I'd probably need two classes - one for the root data structure, and one for the special data structure created
to traverse the first:
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
But when I tried these with some simple datastructures like a list:
-- 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 }
Or a binary tree:
-- 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 }
I couldn't get it to compile, I'd just get a lot of errors like this for each of my Zippable
instance definitions:
Zipper.hs:28:14: Couldn't match expected type `z' against inferred type `ListZipper a' `z' is a rigid type variable bound by the type signature for `zipper' at Zipper.hs:10:20 In the expression: ListZipper {preceding = [], following = as} In the definition of `zipper': zipper as = ListZipper {preceding = [], following = as} In the definition for method `zipper'
So I'm not sure where to go from here. I suspect that my issue is that I'm trying to bind these two instances
together, when the (Zipper z) =>
declaration just wants z
to be any Zipper
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您还可以使用类型同义词族来代替多参数类型类和函数依赖项。 在此类情况下,他们提供了更清晰且更易于理解的解决方案。 在这种情况下,类和实例将变为:
有趣的类型函数对于已经熟悉 Haskell 的人来说是对类型同义词族的一个很好的介绍。 我还写了一篇关于如何输入的文章不久前经常可以使用同义词族来代替函数依赖。
希望这可以帮助!
You can also use type synonym families instead of multi-parameter type classes and functional dependencies. In cases like these they offer a cleaner and easier-to-understand solution. In that case the class and instance would become:
Fun with type functions is an excellent introduction to type synonym families for people already familiar with Haskell. I also wrote an article on how type synonym families can often be used instead of functional dependencies a while ago.
Hope this helps!
(旁白:你的
go'up
命名方案很有创意。Haskell 风格通常是驼峰命名法。)你走在正确的轨道上。 你所写的相当于下面的内容。
,给定
Zipper z
,存在一个zipper :: [a] -> z
。)(对于所有类型
z
定义zipper = ... :: [a] -> ListZipper a
,这显然限制太多。您的代码将通过以下最小更改进行类型检查:
请参阅多参数类型类。 它是 Haskell'98 后的扩展,但 Haskell 实现广泛支持它。
(Aside: your
go'up
naming scheme is... inventive. Haskell style is usually camelCase.)You're on the right track. What you've written is equivalent to the below.
(For all types
z
, givenZipper z
, there exists azipper :: [a] -> z
.)You're tring to define
zipper = ... :: [a] -> ListZipper a
, which is clearly too restrictive.Your code will typecheck with the following minimal changes:
See multi-parameter type classes. It's a post-Haskell'98 extension, but Haskell implementations widely support it.