来自 Int -> Int 的递归方案?

A recursion scheme from Int -> Int?

文件夹身份是

foldr (:) []

更一般地说,使用折叠,您可以破坏结构并以汇总值结束,或者以最终获得相同输出结构的方式注入结构。

[Int] -> [Int] 要么 [Int] -> Int 要么 [Int] -> ?

我想知道是否有与 unfoldr/l 相似的身份。

我知道怎么得到

Int -> [Int]

和unfold/ana.

我正在寻找某种方式从

Int -> Int

使用递归方案。

处理构建中间结构并将其拆除以使该结构不出现在输入或输出中的递归方案是一种同态,在 [= 中拼写为 hylo 12=].

要使用同态,您需要指定代数(消耗递归结构的一步)和余代数(产生递归结构的一步),并且您需要具有数据类型当然,对于您使用的结构类型。

你建议阶乘,让我们看看如何将其写成同态。

查看阶乘的一种方法是从初始 n 开始倒数的数字列表的乘积。在此框架中,我们可以将乘积视为我们的代数,一次拆除列表的一个缺点,将倒计时视为我们的余数,将列表构建为 n 递减。

recursion-schemes 为我们提供了 ListF 作为列表的方便的基本仿函数,因此我们将使用它作为由余数生成并由代数使用的数据类型。它的构造函数是 NilCons,这当然类似于完整列表的构造函数,除了 ListF,就像递归方案中的任何基本结构一样,在该位置使用类型参数该列表将使用实际递归(意味着 Cons :: a -> b -> ListF a b 而不是 (:) :: a -> [a] -> [a])。

所以这决定了我们的类型。现在定义 fact 是一个相当填空的练习:

import Prelude hiding (product)
import Data.Functor.Foldable

product :: ListF Int Int -> Int
product Nil = 1
product (Cons a b) = a * b

countDown :: Int -> ListF Int Int
countDown 0 = Nil
countDown n = Cons n (n - 1)

fact :: Int -> Int
fact = hylo product countDown

从您关于阶乘的评论中得到启发,我们可以注意到自然数可以被视为递归数据结构:

data Nat = Zero | Succ Nat

recursion-schemes机制而言,对应的基仿函数为:

data NatF a = ZeroF | SuccF a
    deriving (Functor)
然而,

NatFMaybe 同构。既然如此,recursion-schemes 方便地使 Maybe 成为 the Natural type from base 的基函子。例如,这里是 ana 专门用于 Natural 的类型:

ana @Natural :: (a -> Maybe a) -> a -> Natural

我们可以用它来写出Natural的身份展开:

{-# LANGUAGE LambdaCase #-}

import Numeric.Natural
import Data.Functor.Foldable

idNatAna :: Natural -> Natural
idNatAna = ana $ \case
    0 -> Nothing
    x -> Just (x - 1)

我们刚刚给ana的余数是project for Naturalproject是展开递归结构一层的函数。就 recursion-schemes 词汇而言,ana project 是身份展开,cata embed 是身份折叠。 (特别是,列表的 projectData.Listuncons,除了它是用 ListF 而不是 Maybe 编码的。)

顺便说一句,阶乘函数可以表示为自然数的同构 ()。我们还可以根据 recursion-schemes:

来实现
fact :: Natural -> Natural
fact = para $ \case
    Nothing -> 1
    Just (predec, prod) -> prod * (predec + 1)

para 在每个递归步骤中提供要折叠的结构的其余部分(如果我们折叠列表,那将是它的尾部)。在这种情况下,我将这样提供的值称为 predec,因为在第 n 个递归步骤中,从底部到顶部 predecn - 1.

请注意, 可能是更有效的实施方式,如果您碰巧关心这一点的话。 (不过我还没有对它们进行基准测试。)