ユニプレートのデモはすでにあるので、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]
基本ファンクターのボイラープレートは大きいですが、比較的驚くことではなく、タイプごとに 1 回であるため、長期的には労力を節約できます。
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
アプローチは簡潔 (1 つのタイプに対して多くの異なるトラバーサルが必要な場合) であり、柔軟 (トラバーサルの順序、空のサブツリーを含めるかどうかなどを完全に制御できます) の両方であることに注意してください。欠点の 1 つは、 の型para
やその他のスキームが一般的すぎて型の推論ができないことです。
geniplate
句uniplate
を入れる必要がないため、よりも邪魔にならないようです。deriving