一旦我有了F-Algebra,我可以用它定义可折叠和可遍历吗?

时间:2018-01-28 15:33:37

标签: haskell category-theory recursion-schemes

我根据Bartosz Milewski的文章(onetwo)定义了 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代数定义可折叠/可遍历实例吗?

在我看来,我不能。

  • 我只能在初始代数上运行一个catamorphism,它是一个nullary类型的构造函数。我给它的代数有一个类型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抽象,并且没有办法一次将隐含的类型变量放在两个地方。

我不知道该怎么做。

2 个答案:

答案 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))),其中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,因此您需要证明定点数据结构是一个仿函数。你可以通过实现一个简单的代数并在其上运行一个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。使用基本仿函数BifunctorExpr)在定点(Functor)上定义Fix Expr。 这种方法也推广到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

现在我们已经有了机器,我们可以有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] 翻转。这样我们可以TreeFFunctor (Fix b), {(1}}就(不存在于公共图书馆中)Bifunctor f等而言。{/ p>

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

中的问题