Haskell 对于具有嵌套值的树数据类型,可遍历实例应该是什么样子?
我三天后要参加Haskell考试,所以我想我应该练习一下,并复习一下过去的考试,其中一个考试的特点是以下树数据类型:Haskell 对于具有嵌套值的树数据类型,可遍历实例应该是什么样子?,haskell,tree,maybe,traversable,Haskell,Tree,Maybe,Traversable,我三天后要参加Haskell考试,所以我想我应该练习一下,并复习一下过去的考试,其中一个考试的特点是以下树数据类型: data Tree a = Leaf1 a | Leaf2 a a | Node (Tree a) (Maybe (Tree a)) deriving (Eq, Ord, Show) 起初,这似乎没有什么挑战性,但后来我意识到我必须为这棵树编写一个可遍历的实例。处理树叶很容易: instance Traversable Tree where traverse f (Leaf
data Tree a = Leaf1 a | Leaf2 a a | Node (Tree a) (Maybe (Tree a)) deriving (Eq, Ord, Show)
起初,这似乎没有什么挑战性,但后来我意识到我必须为这棵树编写一个可遍历的实例。处理树叶很容易:
instance Traversable Tree where
traverse f (Leaf1 a) = Leaf1 <$> f a
traverse f (Leaf2 a b) = Leaf2 <$> f a <*> f b
实例可遍历树,其中
横向f(叶1 a)=叶1 f a
遍历f(叶2 a b)=叶2 f a b
但是,我开始遇到节点问题
traverse f (Node t Nothing) = Node <$> traverse f t <*> Nothing
traverse f (Node l (Just r)) = Node <$> traverse f l <*> Just (traverse f r)
遍历f(节点t Nothing)=节点遍历f t Nothing
遍历f(节点l(正r))=节点遍历f l正(遍历f r)
当然,这些都不起作用,而且我也不知道第二次之后会发生什么。我尝试使用孔,但ghci给我的消息没有多大帮助(我知道问题出在类型上,但我不知道该如何修复它)
以下是我试图编译它时收到的错误消息:
* Couldn't match type `f' with `Maybe'
`f' is a rigid type variable bound by
the type signature for:
traverse :: forall (f :: * -> *) a b.
Applicative f =>
(a -> f b) -> Tree a -> f (Tree b)
at exam.hs:92:3-10
Expected type: f (Maybe (Tree b))
Actual type: Maybe (Maybe (Tree b))
* In the second argument of `(<*>)', namely `Nothing'
In the expression: Node <$> traverse f t <*> Nothing
In an equation for `traverse':
traverse f (Node t Nothing) = Node <$> traverse f t <*> Nothing
* Relevant bindings include
f :: a -> f b (bound at exam.hs:94:12)
traverse :: (a -> f b) -> Tree a -> f (Tree b)
(bound at exam.hs:92:3)
|
94 | traverse f (Node t Nothing) = Node <$> traverse f t <*> Nothing
| ^^^^^^^
*无法将类型“f”与“Maybe”匹配
`f'是一个刚性类型变量,由
以下项的类型签名:
遍历::forall(f::*->*)a b。
应用f=>
(a->f b)->树a->f(树b)
考试时。hs:92:3-10
预期类型:f(可能(树b))
实际类型:可能(可能(树b))
*在“()”的第二个参数中,即“Nothing”
在表达式中:节点遍历f t Nothing
在“导线测量”的方程式中:
遍历f(节点t Nothing)=节点遍历f t Nothing
*相关绑定包括
f::a->f b(在考试时绑定。hs:94:12)
遍历::(a->f b)->树a->f(树b)
(考试时装订。hs:92:3)
|
94 |遍历f(节点t Nothing)=节点遍历f t Nothing
| ^^^^^^^
有人能给我一些关于这个问题的建议或可能的解决方法吗?允许您对数据结构的每个“槽”应用“有效果的函数”,以保持结构的形状。其类型为:
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
它主要依赖于这样一个事实,即“效果”的类型是一种应用程序。Applicatve
提供哪些操作
- 它使我们能够提升纯函数,并将它们应用于
的有效操作 - 它让我们将有效的动作与
结合起来。请注意,第二个参数是有效的操作,而不是纯值():f(a->b)->f a->f b
- 它允许我们使用
获取任何纯值并将其放入有效的上下文中pure::a->fa
Nothing
时,没有任何执行效果,因为没有任何值,但是
仍然需要在右侧执行有效的操作。我们可以使用pure Nothing
使类型适合
当节点只有一个t
时,我们可以使用函数a->fb
遍历树a
类型的子树t
,并以一个动作f(树b)
结束。但是
实际上需要一个f(可能是(树b))
。提升的节点
构造函数让我们期待。我们能做什么
解决方案是使用
将构造函数提升到动作中,这是fmap
的另一个名称
请注意,我们没有更改值的整体“形状”:Nothing
仍然是Nothing
,而Just
仍然是Just
。子树的结构也没有改变:我们递归地遍历它们,但没有修改它们。简单的回答是,您需要使用遍历
进入可能
类型的可遍历的
和可折叠的
实例通常与其函子
实例具有类似的结构。而fmap
将纯函数映射到结构上,将结果与纯构造函数结合起来:
instance Functor Tree where
fmap f (Leaf1 a) = Leaf1 (f a)
fmap f (Leaf2 a1 a2) = Leaf2 (f a1) (f a2)
fmap f (Node ta mta) = Node (fmap f ta) (fmap (fmap f) mta)
注意(fmap(fmap f)mta)
:外部fmap
映射到可能
,而内部映射到树上
:
(fmap
:: (Tree a -> Tree b)
-> Maybe (Tree a) -> Maybe (Tree b))
((fmap
:: (a -> b)
-> Tree a -> Tree b)
f)
mta
遍历
而是将有效的函数映射到结构上,并相应地使用
和
操作符将构造函数提升到应用
:
instance Traversable Tree where
traverse f (Leaf1 a) = Leaf1 <$> f a
traverse f (Leaf2 a1 a2) = Leaf2 <$> f a1 <*> f a2
traverse f (Node ta mta) = Node <$> traverse f ta <*> traverse (traverse f) mta
类似地,foldMap
具有类似的结构,但它不是重建数据类型,而是使用Monoid
实例组合结果:
instance Foldable Tree where
foldMap f (Leaf1 a) = f a
foldMap f (Leaf2 a1 a2) = f a1 <> f a2
foldMap f (Node ta mta) = foldMap f ta <> foldMap (foldMap f) mta
使用DeriveFoldable
、DeriveFunctor
和DeriveTraversable
扩展,您可以在数据类型中添加一个派生(Foldable,Functor,Traversable)
子句,并使用GHC的-ddump-deriv
标志查看生成的代码。我不确定它是否正确(没有为非平凡数据类型编写可遍历实例的经验),但是使用pure Nothing
而不是Nothing
可以避免此类型错误。谢谢,您的解释很容易理解,多亏了您,我才使代码正常工作!
instance Foldable Tree where
foldMap f (Leaf1 a) = f a
foldMap f (Leaf2 a1 a2) = f a1 <> f a2
foldMap f (Node ta mta) = foldMap f ta <> foldMap (foldMap f) mta
> traverse (\ x -> print x *> pure (x + 1)) (Node (Leaf1 10) (Just (Leaf2 20 30)))
10
20
30
Node (Leaf1 11) (Just (Leaf2 21 31))