Since Uniplate demonstration is already there, here is an implementation using recursion-schemes
library for completeness sake:
{-# 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]
The base functor boilerplate is large, but relatively unsurprising and may save you effort in long run as it's once per type.
And here is yet another implementation using geniplate
library:
{-# 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)
And here is a shortened version of @bheklilr explicitly recursive approach which one probably expects from a newcomer (I used (++)
for symmetry):
allSubTrees3 :: BinaryT a -> [BinaryT a]
allSubTrees3 Empty = []
allSubTrees3 this @ (Node left _ right) = [this] ++ leftSubs ++ rightSubs where
leftSubs = allSubTrees3 left
rightSubs = allSubTrees3 right
Note that it lists the root but doesn't list empty subtrees, but it's easily changeable.
I wonder what are advantages and disadvantages of different approaches. Is uniplate
somehow more or less type safe then other approaches?
Note that recursion-schemes
approach is both concise (if you need many different traversals for one type) and flexible (you have full control over traversal order, whether to include empty subtrees etc). One disadvantage is that type of para
and other schemes is too general to allow type inference, so a type signature is often needed to disambiguate.
geniplate
seems to be less intrusive than uniplate
, as there's no need to put deriving
clauses.