【问题标题】:When is a composition of catamorphisms a catamorphism?什么时候变质的组合是变质?
【发布时间】:2012-08-19 15:48:04
【问题描述】:

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

一般来说,变质在合成下是封闭的,这是不正确的

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

假设我有两个基本仿函数 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: 正如 colah 指出的那样,这已经足够但不是必需的。)

编辑 3:Haskell-Cafe 上的 Wren Thornton 补充道:“如果你有正确的分配属性(正如 colah 所建议的那样),那么对于特定情况,事情就会解决。但是,拥有正确的分配属性通常相当于在某个适当相关的类别中进行自然转换;因此,问题只是推迟到适当相关的类别是否始终存在,以及我们是否可以形式化“适当相关”的含义。”

【问题讨论】:

    标签: haskell functional-programming composition fold catamorphism


    【解决方案1】:

    什么时候是合成 (fold2 g) 。 (fold1 f) :: μF1 -> A 变质?

    当存在F1-代数h :: F1 A -> A 时,使得fold1 h = fold2 g . fold1 f

    要了解变态在合成下通常不是封闭的,请考虑以下类型级不动点、代数和变态的通用定义:

    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
    

    为了合成变质,我们需要

    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 的结果到第二个不动点的态射函子:

    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
    

    【讨论】:

    • 很明显,但还能说更多吗?你的意思是h :: F1 A -> A
    • 您还需要什么?你的意思是你在代数组成一个新代数的条件之后? (我修正了错字。)
    • 我追求的条件是两个变质的组合是一种比变质的定义更具有启发性的变质。您的扩展评论有帮助,谢谢。
    • 我在我的答案中添加了一个这样的条件示例。不过,可能会有更通用/更有用的条件。
    【解决方案2】:

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

    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
    

    让我们再考虑几个变质的例子:

    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
    

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

    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 替换为您想要的任何内容,然后您就拥有了新的变质。

    1. 如果第一个 catamorphism 生成一个数据结构而不查看其子级会发生什么,则两个 catamorphism 将组成一个 catamorphism。

    除此之外,事情变得更加复杂。让我们区分普通的构造函数参数和“递归参数”(我们将用 % 符号标记)。所以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) ..,那也可以。

    我预计,从这里开始,规则会变得更加混乱。

    【讨论】:

      【解决方案3】:

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

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

      但是,一些特殊的变态会产生与输入相同类型的结果。一个这样的例子是map f(对于某些给定的函数f)。在解构原始结构的同时,它还创建了一个新列表作为其结果。 (实际上,map f 既可以看作是变质,也可以看作是anamorphism。)所以如果你有这样一类特殊的变质,你可以组合它们。

      【讨论】:

        【解决方案4】:

        如果我们考虑语义等价,两个变态的组合是变态,而第一个是变态:

        cata1 . hylo1 = cata2
        

        例如(Haskell):

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

        【讨论】:

          猜你喜欢
          • 2011-08-09
          • 1970-01-01
          • 1970-01-01
          • 2018-02-20
          • 2017-01-11
          • 2021-07-25
          • 2020-06-23
          • 1970-01-01
          • 2015-02-10
          相关资源
          最近更新 更多