我不会说我推荐以下内容,但为了完整性实际上可以定义这样的 Functor。
Functor 类型类要求您可以将fmap any 函数添加到您的Functor 中。这通常意味着很难确保需要类型类实例的不变量。但是,我们可以稍微扭曲这种情况,实际上最终得到一个Functor 实例。在实践中,这意味着我们可以使用类型系统来确保我们将重新平衡推迟到更方便的时间。
首先,我们将介绍对上述类型的需求。特别是,我们将给它一个保持平衡的Monoid 实例。这很好,因为Monoid 不需要我们的容器是多态的。
instance Ord a => Monoid (BalancedTree a) where
mempty = Leaf
mappend Leaf Leaf = Leaf
mappend Leaf b = b
mappend b Leaf = b
mappend (Node a l1 r1) (Node b l2 r2) = ... -- merge and rebalance here
现在,使用这个实例,我们可以编写函数,几乎对应于Monad 的Monad 实例BinaryTree。特别是,我们需要它来将我们的新树组合为使用bindBin 构建的,几乎 版本的(>>=) 在二叉搜索树上。
returnBin :: a -> BinaryTree a
returnBin a = Node a Leaf Leaf
bindBin :: Ord b => BinaryTree a -> (a -> BinaryTree b) -> BinaryTree b
bindBin Leaf _ = Leaf
bindBin (Node a l r) f = bindBin l f <> f a <> bindBin r f
然后我们介绍这个很奇怪的类型(需要RankNTypes扩展)
newtype FBinaryTree a =
FBinaryTree (forall r . Ord r => (a -> BinaryTree r) -> BinaryTree r)
对此有很多思考方式,但我们只注意到FBinaryTree a 和BinaryTree a 之间存在同构,基本上,returnBin 和bindBin 见证了这一点。
toF :: BinaryTree a -> FBinaryTree a
toF bt = FBinaryTree (bindBin bt)
fromF :: Ord a => FBinaryTree a -> BinaryTree a
fromF (FBinaryTree k) = k returnBin
最后,由于FBinaryTree 继承了Cont monad 或Yoneda 引理类型的一些属性,我们可以为FBinaryTree 定义一个Functor 实例!
instance Functor FBinaryTree where
fmap f (FBinaryTree c) = FBinaryTree (\k -> c (k . f))
所以现在,我们要做的就是将我们的BinaryTrees 转换为FBinaryTrees,在那里执行我们的Functor 操作,然后根据需要返回到BinaryTree。一帆风顺,对吧?
嗯,差不多。事实证明,我们为此付出了巨大的效率代价。特别是,当使用像 FBinaryTree 这样的类型时,很容易发生指数级爆炸。我们可以通过使用不时发送FBinaryTree 到BinaryTree 来避免这些情况
optimize :: Ord a => FBinaryTree a -> FBinaryTree a
optimize = toF . fromF
正如类型显示的那样,它要求我们在那里有一个Ord 实例。事实上,代码将使用那里的Ord 实例来执行所需的重新平衡。