我根据Bartosz Milewski的文章(one,two)定义了 F-Algebra :
(这并不是说我的代码是Bartosz思想的一个确切体现,它仅仅是我对它们的有限理解,而且任何缺陷都是我自己的。)
module Algebra where
data Expr a = Branch [a] | Leaf Int
instance Functor Expr where
fmap f (Branch xs) = Branch (fmap f xs)
fmap _ (Leaf i ) = Leaf i
newtype Fix a = Fix { unFix :: a (Fix a) }
branch = Fix . Branch
leaf = Fix . Leaf
-- | This is an example algebra.
evalSum (Branch xs) = sum xs
evalSum (Leaf i ) = i
cata f = f . fmap (cata f) . unFix
我现在可以做任何我想做的事情,例如,总结一下叶子:
λ cata evalSum $ branch [branch [leaf 1, leaf 2], leaf 3]
6
这是我专门为这个问题编写的一个人为的例子,但我实际上尝试了一些不那么微不足道的事情(例如用任意数量的变量来评估和简化多项式),它就像一个魅力。人们可能确实折叠和替换结构的任何部分,因为运行 catamorphism ,并使用适当选择的代数。所以,我很确定F-Algebra包含了一个Foldable,它甚至看起来也包含了Traversable。
现在,我可以用F代数定义可折叠/可遍历实例吗?
在我看来,我不能。
a b -> b
而不是a -> b
,也就是说,在“in”和“out”类型之间存在功能依赖。 Algebra a => Foldable a
。如果不这样做,那就一定不可能。在我看来,我无法根据F代数定义Foldable
,因为Expr
必须在两个变量中为Functor
:一个用于< em> carrier ,另一个用于值,然后是Foldable
在第二个。因此, bifunctor 可能更合适。我们也可以用一个bifunctor构造一个F-代数:
module Algebra2 where
import Data.Bifunctor
data Expr a i = Branch [a] | Leaf i
instance Bifunctor Expr where
bimap f _ (Branch xs) = Branch (fmap f xs)
bimap _ g (Leaf i ) = Leaf (g i)
newtype Fix2 a i = Fix2 { unFix2 :: a (Fix2 a i) i }
branch = Fix2 . Branch
leaf = Fix2 . Leaf
evalSum (Branch xs) = sum xs
evalSum (Leaf i ) = i
cata2 f g = f . bimap (cata2 f g) g . unFix2
它的运行方式如下:
λ cata2 evalSum (+1) $ branch [branch [leaf 1, leaf 2], leaf 3]
9
但我还是无法定义可折叠的。它的类型如下:
instance Foldable \i -> Expr (Fix2 Expr i) i where ...
不幸的是,没有人在类型上得到lambda抽象,并且没有办法一次将隐含的类型变量放在两个地方。
我不知道该怎么做。
答案 0 :(得分:16)
在评估了所有子项之后,F-algebra定义了用于评估递归数据结构的单个级别的配方。 Foldable
定义了一种评估(不一定是递归)数据结构的方法,前提是您知道如何将存储在其中的值转换为monoid的元素。
要为递归数据结构实现foldMap
,您可以从定义代数开始,代数的载体是幺半群。您将定义如何将叶转换为monoidal值。然后,假设节点的所有子节点都被评估为monoidal值,您将定义一种在节点内组合它们的方法。一旦定义了这样的代数,就可以运行一个catamorphism来评估整个树的foldMap
。
因此,您的问题的答案是,要为定点数据结构创建Foldable
实例,您必须定义一个适当的代数,其载波是一个幺半群。
编辑:这是可折叠的实现:
data Expr e a = Branch [a] | Leaf e
newtype Ex e = Ex { unEx :: Fix (Expr e) }
evalM :: Monoid m => (e -> m) -> Algebra (Expr e) m
evalM _ (Branch xs) = mconcat xs
evalM f (Leaf i ) = f i
instance Foldable (Ex) where
foldMap f = cata (evalM f) . unEx
tree :: Ex Int
tree = Ex $ branch [branch [leaf 1, leaf 2], leaf 3]
x = foldMap Sum tree
实现Traversable
作为一种变形更为复杂,因为您希望结果不仅仅是一个摘要 - 它必须包含完整的重建数据结构。因此,代数的载体必须是traverse
的最终结果的类型,即(f (Fix (Expr b)))
,其中f
是Applicative
。
tAlg :: Applicative f => (e -> f b) -> Algebra (Expr e) (f (Fix (Expr b)))
这是代数:
tAlg g (Leaf e) = leaf <$> g e
tAlg _ (Branch xs) = branch <$> sequenceA xs
这就是您实施traverse
的方式:
instance Traversable Ex where
traverse g = fmap Ex . cata (tAlg g) . unEx
Traversable
的超类是Functor
,因此您需要证明定点数据结构是一个仿函数。你可以通过实现一个简单的代数并在其上运行一个catamorphism来实现它:
fAlg :: (a -> b) -> Algebra (Expr a) (Fix (Expr b))
fAlg g (Leaf e) = leaf (g e)
fAlg _ (Branch es) = branch es
instance Functor Ex where
fmap g = Ex . cata (fAlg g) . unEx
(Michael Sloan帮我写了这段代码。)
答案 1 :(得分:4)
非常好,您使用了Bifunctor
。使用基本仿函数Bifunctor
(Expr
)在定点(Functor
)上定义Fix Expr
。
这种方法也推广到Bifoldable
和Bitraversable
(现在也在base
)。
让我们看看如何使用recursion-schemes
。
它看起来有点不同,因为我们定义了正常的递归类型,
说Tree e
,以及它的基础仿函数:Base (Tree e) = TreeF e a
有两个函数:
project :: Tree e -> TreeF e (Tree e)
和embed :: TreeF e (Tree e) -> Tree e
。
递归机制可以使用 TemplateHaskell :
请注意,我们有Base (Fix f) = f
(project = unFix
,embed = Fix
),
因此,我们可以使用refix
将Tree e
转换为Fix (TreeF e)
并返回。但
我们不需要使用Fix
,因为我们可以直接cata
Tree
!
首先包括:
{-# LANGUAGE TemplateHaskell, KindSignatures, TypeFamilies, DeriveFunctor, DeriveFoldable, DeriveTraversable #-}
import Data.Functor.Foldable
import Data.Functor.Foldable.TH
import Data.Bifunctor
import Data.Bifoldable
import Data.Bitraversable
然后是数据:
data Tree e = Branch [Tree e] | Leaf e deriving Show
-- data TreeF e r = BranchF [r] | LeafF e
-- instance Traversable (TreeF e)
-- instance Foldable (TreeF e)
-- instance Functor (TreeF e)
makeBaseFunctor ''Tree
现在我们已经有了机器,我们可以有catamorphisms
cata :: Recursive t => (Base t a -> a) -> t -> a
cata f = c where c = f . fmap c . project
或(稍后我们需要)
cataBi :: (Recursive t, Bifunctor p, Base t ~ p x) => (p x a -> a) -> t -> a
cataBi f = c where c = f . second c . project
首先是Functor
个实例。 OP Bifunctor
的{{1}}实例正如OP所写,
请注意TreeF
如何自行消失。
Functor
毫不奇怪,instance Bifunctor TreeF where
bimap f _ (LeafF e) = LeafF (f e)
bimap _ g (BranchF xs) = BranchF (fmap g xs)
instance Functor Tree where
fmap f = cata (embed . bimap f id)
for fixpoint可以用Foldable
的基础来定义
算符:
Bifoldable
最后instance Bifoldable TreeF where
bifoldMap f _ (LeafF e) = f e
bifoldMap _ g (BranchF xs) = foldMap g xs
instance Foldable Tree where
foldMap f = cata (bifoldMap f id)
:
Traversable
正如您所看到的,定义非常简单,并且遵循类似的定义 图案。
实际上,我们可以为每个定位点定义instance Bitraversable TreeF where
bitraverse f _ (LeafF e) = LeafF <$> f e
bitraverse _ g (BranchF xs) = BranchF <$> traverse g xs
instance Traversable Tree where
traverse f = cata (fmap embed . bitraverse f id)
- 类似函数
仿函数是traverse
。
Bitraversable
在这里,我们使用traverseRec
:: ( Recursive t, Corecursive s, Applicative f
, Base t ~ base a, Base s ~ base b, Bitraversable base)
=> (a -> f b) -> t -> f s
traverseRec f = cataBi (fmap embed . bitraverse f id)
使类型签名更漂亮:没有cataBi
它意味着&#34;&#34;暗示&#34;按Functor (base b)
。顺便说一下,这是一个不错的功能
类型签名是实现的三倍。
总而言之,我必须提到Haskell中的Bitraversable base
并不完美:
我们使用最后一个参数来修复base-functor:
Fix
因此Bartosz需要在他的答案中定义Fix :: (* -> *) -> * -- example: Tree e ~ Fix (TreeF e)
以使种类对齐,
然而,修复第一个论点会更好:
Ex
其中Fix :: (* -> k) -> k -- example: Tree e = Fix TreeF' e
,即带有索引的data TreeF' a e = LeafF' e | BranchF' [a]
翻转。这样我们可以TreeF
来Functor (Fix b)
,
{(1}}就(不存在于公共图书馆中)Bifunctor f
等而言。{/ p>
你可以阅读我对此失败的尝试以及Edward Kmett的评论 关于https://github.com/ekmett/recursion-schemes/pull/23
中的问题