catamorphisms的构成何时是一个变形?

时间:2012-08-24 04:50:59

标签: haskell functional-programming composition fold catamorphism

来自http://research.microsoft.com/en-us/um/people/emeijer/Papers/meijer94more.pdf的第3页:

  

一般情况下,在构图

下封闭症状是不正确的

catamorphisms在什么条件下构成catamorphism?更具体地说(假设我正确地理解了陈述):

假设我有两个基础仿函数FG,并为每个仿函数foldF :: (F a -> a) -> (μF -> a)foldG :: (G a -> a) -> (μG -> a)折叠。

现在假设我有两个代数a :: F μG -> μGb :: G X -> X

组合(foldG b) . (foldF a) :: μF -> X何时是一个变形?


编辑:我猜测,基于dblhelix的扩展答案:outG . a :: F μG -> G μG必须是某些自然转换μG η :: F a -> G a的组件。我不知道这是否正确。 (编辑2:正如科拉指出的那样,这已足够,但并非必要。)

编辑3: Wask Thornton在Haskell-Cafe上补充说:“如果你有合适的分配属性(正如科拉所建议的那样),那么事情就会适用于特定情况。但是,拥有正确的分配性属性通常相当于某种适当相关类别的自然转换;因此,只是将问题推迟到适当相关的类别是否总是存在,以及我们是否可以将“适当相关”的含义形式化。“

4 个答案:

答案 0 :(得分:5)

  

何时为组合物(fold2g)。 (fold1 f)::μF1 - >一个catamorphism?

当存在F1 - 代数h :: F1 A -> Afold1 h = fold2 g . fold1 f

要看到catamorphisms一般不在组合下关闭,请考虑以下类型级定点,代数和变形的一般定义:

newtype Fix f = In {out :: f (Fix f)}

type Algebra f a = f a -> a

cata :: Functor f => Algebra f a -> Fix f -> a
cata phi = phi . fmap (cata phi) . out

对于catamorphisms撰写我们需要

algcomp ::  Algebra f (Fix g) -> Algebra g a -> Algebra f a

现在尝试编写此功能。它需要两个函数作为参数(分别为f (Fix g) -> Fix gg a -> a类型)和类型f a的值,并且需要生成类型a的值。你会怎么做?要生成a类型的值,您唯一的希望是应用g a -> a类型的函数,但是我们陷入困境:我们无法将f a类型的值转换为类型g a的值,有吗?

我不确定这是否对你的目的有用,但是一个条件的例子就是我们可以从第二个cata的结果到第二个cata的固定点有一个态射。算符:

algcomp' :: (Functor f, Functor g) =>
            (a -> Fix g) -> Algebra f (Fix g) -> Algebra g a -> Algebra f a
algcomp' h phi phi' = cata phi' . phi . fmap h

答案 1 :(得分:4)

(免责声明:这超出了我的专业领域。我相信我是正确的(在不同的地方提供了警告),但是......自己验证。)

可以将catamorphism视为用其他函数替换数据类型的构造函数的函数。

(在本例中,我将使用以下数据类型:

data [a] = [] | a : [a]

data BinTree a = Leaf a | Branch (BinTree a) (BinTree a)

data Nat = Zero | Succ Nat

例如:

length :: [a] -> Nat
length = catamorphism
     []   -> 0
     (_:) -> (1+)

(可悲的是,catamorphism {..}语法在Haskell中不可用(我在Pola中看到类似的东西)。我一直想为它写一个quasiquoter。)

那么,length [1,2,3]是什么?

length [1,2,3]
length (1 : 2 : 3 : [])
length (1:  2:  3:  [])
        1+ (1+ (1+ (0 )))
        3

那就是说,由于后来会变得明显的原因,将它定义为平凡的等价物是更好的:

length :: [a] -> Nat
length = catamorphism
     []   -> Zero
     (_:) -> Succ

让我们再考虑一些示例性的catamorphisms:

map :: (a -> b) -> [a] -> b
map f = catamorphism
     []   -> []
     (a:) -> (f a :)

binTreeDepth :: Tree a -> Nat
binTreeDepth = catamorphism
     Leaf _ -> 0
     Branch -> \a b -> 1 + max a b

binTreeRightDepth :: Tree a -> Nat
binTreeRightDepth = catamorphism
     Leaf _ -> 0
     Branch -> \a b -> 1 + b

binTreeLeaves :: Tree a -> Nat
binTreeLeaves = catamorphism
     Leaf _ ->  1
     Branch -> (+)

double :: Nat -> Nat
double = catamorphism
     Succ -> Succ . Succ
     Zero -> Zero

其中许多可以很好地组合以形成新的catamorphisms。例如:

double . length . map f = catamorphism
     []   -> Zero
     (a:) -> Succ . Succ

double . binTreeRightDepth = catamorphism
     Leaf a -> Zero
     Branch -> \a b -> Succ (Succ b)

double . binTreeDepth也有效,但在某种意义上它几乎是一个奇迹。

double . binTreeDepth = catamorphism
     Leaf a -> Zero
     Branch -> \a b -> Succ (Succ (max a b))

这只能起作用,因为double分布在max上......这纯属巧合。 (对于double . binTreeLeaves也是如此。)如果我们将max替换为不能很好地加倍的东西......那么,让我们自己定义一个新朋友(不相处和其他人一样)。对于double未分发的二元运算符,我们将使用(*)

binTreeProdSize :: Tree a -> Nat
binTreeProdSize = catamorphism
     Leaf _ -> 0
     Branch -> \a b -> 1 + a*b

让我们尝试为两个组合构成两个构造的充分条件。很明显,任何catamorphism都很乐意与lengthdoublemap f组成,因为它们会在不查看子结果的情况下生成数据结构。例如,在length的情况下,您只需将SuccZero替换为您想要的内容,就可以获得新的catamorphism。

  1. 如果第一个变形现象产生一个数据结构而没有看到它的孩子会发生什么,那么两个同态就会形成一种变形。
  2. 除此之外,事情变得更加复杂。让我们区分正常的构造函数参数和“递归参数”(我们将使用%符号标记)。所以Leaf a没有递归参数,但Branch %a %b没有。让我们使用构造函数的“递归固定”一词来指代它具有的递归参数的数量。 (我已经完成了这两个术语!我不知道什么是合适的术语,如果有的话!在其他地方使用它们时要小心!)

    如果第一个catamorphism映射到零递归固定构造函数,一切都很好!

                   a               |            b            |     cata(b.a)
    ===============================|=========================|================
           F a %b %c .. -> Z       |      Z -> G a b ..      |      True
    

    如果我们将孩子直接映射到一个新的构造函数,我们也很好。

                   a               |            b            |     cata(b.a)
    ===============================|=========================|=================
       F a %b %c .. -> H %c %d ..  |   H %a %b -> G a b ..   |       True
    

    如果我们将一个构造函数映射到递归固定...

                   a               |            b            |     cata(b.a)
    ===============================|=========================|=================
     F a %b %c .. -> A (f %b %c..) |     A %a -> B (g %a)    |    Implied by g
                                   |                         | distributes over f
    

    但它不是iff。例如,如果g1 g2存在g (f a b..) = f (g1 a) (g2 b) ..,那也可以。

    从这里开始,我希望这些规则会变得更加混乱。

答案 2 :(得分:3)

Catamorphisms将数据结构解构为结果值。因此,一般来说,当你应用一个变形时,结果是完全不同的,你不能对它应用另一个变形。

例如,对[Int]的所有元素求和的函数是一个catamorphism,但结果是Int。没有办法如何在其上应用另一个变形。

但是,某些特殊的catamorphisms会创建与输入相同类型的结果。一个这样的例子是map f(对于某些给定的函数f)。虽然它解构了原始结构,但它也会创建一个新列表作为结果。 (实际上,map f既可以被视为一种变形也可以被视为anamorphism。)因此,如果你有这样一类特殊的变形,你可以将它们组合起来。

答案 3 :(得分:2)

如果我们考虑语义等价,当第一个是一个hylomorphism时,两个catamorphisms的组成是一个catamorphism:

cata1 . hylo1 = cata2

例如(Haskell):

sum . map (^2) = foldl' (\x y -> x + y^2) 0