一旦我有了 F 代数,我可以用它来定义 Foldable 和 Traversable 吗?

Once I have an F-Algebra, can I define Foldable and Traversable in terms of it?

我根据 Bartosz Milewski 的文章 (one, two):

定义了一个 F-代数

(这并不是说我的代码完全体现了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

这是我专门为这个问题编造的一个人为的例子,但我实际上尝试了一些不那么琐碎的事情(比如评估和简化具有任意数量变量的多项式)并且它非常有效。 One may indeed fold and replace any parts of a structure as one 运行s a catamorphism through, with a suitably chosen algebra.所以,我很确定 F-Algebra 包含 Foldable,它甚至似乎也包含 Traversable。

现在,我可以根据 F 代数定义 Foldable / Traversable 实例吗?

我觉得我做不到。

在我看来,我无法根据 F 代数定义 Foldable,因为 Expr 必须是两个变量中的 Functor:一个对于 carrier,另一个对于 values,然后是第二个 Foldable。所以,可能 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 抽象,也没有办法同时将隐含类型变量放在两个地方。

我不知道该怎么办。

F 代数定义了一个方法,用于在评估完所有子项后评估递归数据结构的单个级别。 Foldable 定义了一种评估(不一定是递归的)数据结构的方法,前提是您知道如何将存储在其中的值转换为幺半群的元素。

要为递归数据结构实现foldMap,您可以从定义一个代数开始,其载体是幺半群。您将定义如何将叶子转换为幺半群值。然后,假设一个节点的所有子节点都被评估为幺半群值,您将定义一种将它们组合在一个节点内的方法。一旦你定义了这样一个代数,你就可以 运行 一个变形来评估整个树的 foldMap

所以你的问题的答案是,要为定点数据结构创建一个 Foldable 实例,你必须定义一个合适的代数,其载体是幺半群。

编辑:这是 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))),其中 fApplicative

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,所以需要证明定点数据结构是一个函子。您可以通过实现一个简单的代数并 运行 在其上进行变形来做到这一点:

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 帮我写了这段代码。)

非常好,您使用了 Bifunctor。使用基本函子 (Expr) 的 Bifunctor 在定点 (Fix Expr) 上定义 Functor。 这种方法也适用于 BifoldableBitraversable(它们现在在 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) = fproject = unFixembed = Fix), 因此我们可以使用 refixTree 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

现在我们有了机器,我们可以进行变形

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 实例。 TreeFBifunctor 实例如 OP 所写, 请注意 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)

毫不奇怪,可以根据基数的Bifoldable定义固定点的Foldable 仿函数:

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 使类型签名更漂亮:没有 Functor (base b) 作为 Bitraversable base "implied"。顺便说一句,这是一个很好的功能,因为它 类型签名比实现长三倍)。

总而言之,我必须提到 Haskell 中的 Fix 并不完美: 我们使用最后一个参数来修复 base-functor:

Fix :: (* -> *) -> * -- example: Tree e ~ Fix (TreeF e)

因此 Bartosz 需要在他的回答中定义 Ex 以使类型对齐, 然而,修复第一个参数会更好:

Fix :: (* -> k) -> k -- example: Tree e = Fix TreeF' e

其中 data TreeF' a e = LeafF' e | BranchF' [a],即 TreeF 带有索引 翻转。这样我们就可以根据 Bifunctor f 得到 Functor (Fix b)Bifunctor (Fix b)(不存在于公共库中)Trifunctor

您可以阅读我失败的尝试以及 Edward Kmett 的评论 关于 https://github.com/ekmett/recursion-schemes/pull/23

中的问题