由于 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
方法既简洁(如果您需要对一种类型进行许多不同的遍历)又灵活(您可以完全控制遍历顺序,是否包含空子树等)。一个缺点是 type ofpara
和其他方案过于笼统而无法进行类型推断,因此通常需要类型签名来消除歧义。
geniplate
似乎比 更具侵入性uniplate
,因为不需要放置deriving
子句。