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

我三天后要参加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 (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))