由于 Uniplate 演示已经存在,为了完整起见,这里是使用 recursion-schemes 库的实现:
{-# LANGUAGE DeriveFunctor, TypeFamilies #-}
import Data.Functor.Foldable
data BinaryT a
= Empty
| Node (BinaryT a) a (BinaryT a)
deriving (Eq, Show)
data BinaryTBase a b
= BaseEmpty
| BaseNode b a b
deriving (Functor)
type instance Base (BinaryT a) = BinaryTBase a
instance Foldable (BinaryT b) where
project Empty = BaseEmpty
project (Node a b c) = BaseNode a b c
instance Unfoldable (BinaryT b) where
embed BaseEmpty = Empty
embed (BaseNode a b c) = Node a b c
allSubtrees :: BinaryT a -> [BinaryT a]
allSubtrees = para phi where
phi BaseEmpty = []
phi (BaseNode (l, ll) v (r, rr)) = ll ++ rr ++ [Node r v l]
基本函子样板很大,但相对而言并不令人惊讶,并且从长远来看可能会节省您的工作量,因为它是每种类型一次。
这是另一个使用 geniplate 库的实现:
{-# LANGUAGE TemplateHaskell #-}
import Data.Generics.Geniplate
data BinaryT a =
Empty
| Node (BinaryT a) a (BinaryT a)
deriving (Eq, Show)
allSubTrees :: BinaryT a -> [BinaryT a]
allSubTrees = $(genUniverseBi 'allSubTrees)
这里是@bheklilr 显式递归方法的缩短版本,人们可能期望新人提供这种方法(我使用(++) 表示对称):
allSubTrees3 :: BinaryT a -> [BinaryT a]
allSubTrees3 Empty = []
allSubTrees3 this @ (Node left _ right) = [this] ++ leftSubs ++ rightSubs where
leftSubs = allSubTrees3 left
rightSubs = allSubTrees3 right
请注意,它列出了根但不列出空子树,但它很容易更改。
我想知道不同方法的优缺点是什么。 uniplate 在某种程度上比其他方法更安全吗?
请注意,recursion-schemes 方法既简洁(如果您需要对一种类型进行许多不同的遍历)又灵活(您可以完全控制遍历顺序,是否包含空子树等)。一个缺点是para 和其他方案的类型过于笼统,无法进行类型推断,因此通常需要类型签名来消除歧义。
geniplate 似乎没有uniplate 那样具有侵入性,因为不需要添加deriving 子句。