Haskell 递归

问题描述

我正在努力理解下面代码的逻辑。我知道代码将返回从第一个n斐波那契数列列表,例如fib 3 将产生 [2,1,0]。我不明白“n”是如何在 (x:y:xs) 中拆分的。

如果您对此有所了解,我将不胜感激。

谢谢

fib 1 = [1,0]
fib n = x + y : (x:y:xs)
    where (x:y:xs) = fib (n-1)

解决方法

您对代码“如何”拆分 fib 返回的列表的评论我无法回答,因为我不了解 GHC 的所有内部结构。这个过程称为模式匹配。在 Python 和其他你可能熟悉的语言中,可以这样做

a,b = (1,2)
# a == 1
# b == 2

你的函数类型

fib :: Int -> [Int]

所以你可以使用模式匹配来提取它返回的列表的头部、下一个头部和下一个尾部,这就是

where (x:y:xs) = fib (n-1)

也许一个混乱的地方是重建列表的地方,以便它可以附加到您返回的列表的其余部分。你的函数也可以这样写

fib 1 = [1,0]
fib n = x + y : (fib (n-1))
    where (x:y:xs) = fib (n-1)
,

我不明白如何将 n 拆分为 (x:y:xs)

n 没有被拆分;由 fib 生成的列表正在拆分。

原代码:

fib 1 = [1,0]
fib n = x + y : (x:y:xs)
    where (x:y:xs) = fib (n-1)

等价于以下内容,删除了一些语法糖:

fib n =
  if n == 1
    then [1,0]
    else case fib (n - 1) of
      x : (y : xs) -> (x + y) : (x : (y : xs))
      _ -> error "pattern match failure"

由于这段代码只是对列表进行解构,然后重建一个相同的列表,case 分支也可以使用“as”模式编写,例如:res@(x : y : xs) -> (x + y) : res

所以 fib 是一个函数,它接受一个参数 n,它可以是任何数字类型。在 n 为 1 的基本情况下,代码只返回一个常量列表 [1,0]。在递归情况下,fib 以递归方式调用自身,将 n 替换为 n - 1。结果将是一个列表,因此该函数然后在该列表上模式匹配以提取其组件。

在模式x : y : xs(:) x ((:) y xs)的语法糖)中,运算符(:) :: a -> [a] -> [a]是非空列表的数据构造函数,而x、{ {1}} 和 y 是变量;所以这相当于说“如果输入(xs的结果)是非空的,那么将其命名为fib (n - 1);如果它的尾部非空,则分别命名该 xy 的头部和尾部”。换句话说,如果它是一个至少两个元素的列表,那么调用第一个元素xs,第二个x,剩余的y(其中可能为空)。

事实上,它可以以这种显式方式实现,就像在缺乏模式匹配的语言中一样,通过使用守卫或 xs 表达式。结果非常笨拙且容易出错,但它可能有助于说明如何在精神上分解它:

if
fib n

  | n == 1
    = [1,0]

  | let temp1 = fib (n - 1),not (null temp1),let x = head temp1,let temp2 = tail temp1,not (null temp2),let y = head temp2,let xs = tail temp2
    = x + y : temp1

  | otherwise
    = error "pattern match failure"

显然模式匹配要简单得多!

这里有一个示例,说明原始代码如何对您提供的示例输入进行评估,fib n = if n == 1 then [1,0] else let temp1 = fib (n - 1) in if not (null temp1) then let x = head temp1 temp2 = tail temp1 in if not (null temp2) then let y = head temp2 xs = tail temp2 in x + y : temp1 else error "pattern match failure" else error "pattern match failure"

  • 评估:fib 3
    • 将方程 #2 与 fib 3 = n₀ 匹配:3
    • 评估:let (x₀ : y₀ : xs₀) = fib (3 - 1) in x₀ + y₀ : (x₀ : y₀ : xs₀)
      • 将方程 #2 与 fib 2 = n₁ 匹配:2
      • 评估:let (x₁ : y₁ : xs₁) = fib (2 - 1) in x₁ + y₁ : (x₁ : y₁ : xs₁)
        • 匹配方程 #1:fib 1
      • 替换:[1,0]
      • let (x₁ : y₁ : xs₁) = 1 : 0 : [] in x₁ + y₁ : (x₁ : y₁ : xs₁) = letx₁ = 1y₁ = 0 评估 xs₁:{{1} }
    • 替换:[]
    • 1 + 0 : (1 : 0 : []) = let (x₀ : y₀ : xs₀) = 1 : 1 : [0] in x₀ + y₀ : (x₀ : y₀ : xs₀)let = x₀1 = y₀ 评估 1:{{1} }
  • 列表的语法糖:xs₀

还有一个图表,展示了它如何构建一个列表,其中每个元素的值都指向随后的两个元素:

[0]

ASCII 版本:

1 + 1 : (1 : 1 : [0])

注意对 [2,1,0] 的调用:如果您尝试评估不详尽的模式匹配,它将抛出异常。在这种情况下, ┌─────┬───────────┬───────┐ ┌─────┬───────────┬───────────┐ ┌─────┬───┬─────┐ ┌─────┬───┬───┐ ┌────┐ … fib 3───▶ (:) │ (+) x₁ y₁ │ fib 2─┼─▶ (:) │ (+) x₀ y₀ │ xs₁/fib 1─┼─▶ (:) │ 1 │ xs₀─┼─▶ (:) │ 0 │ ○─┼─▶ [] │ └─────┴─────┼──┼──┴───────┘ └─────┴──▲──┼──┼──┴───────────┘ └─────┴─▲─┴─────┘ └─────┴─▲─┴───┘ └────┘ └──┼─────────────────────┘ │ └────────────────────────┼─────────────────┘ └────────────────────────┴───────────────────────────┘ 将始终至少包含两个元素,因此 +-----+-----------+-------+ +-----+-----------+-----------+ +-----+---+-----+ +-----+---+---+ +----+ | | | | | | | | | | | | | | | | | | … fib 3---> (:) | (+) x1 y1 | fib 2-+---> (:) | (+) x0 y0 | xs1/fib 1-+---> (:) | 1 | xs0-+---> (:) | 0 | o-+---> [] | | | | | | | | | | | | | | | | | | | | | | | +-----+-----+--+--+-------+ +-----+--^--+--+--+-----------+ +-----+-^-+-----+ +-----+-^-+---+ +----+ | | | | | | | +--+-----------------------+ | +--------------------------+-------------------+ | | | +--------------------------+-----------------------------+ 绑定是安全的,但请考虑如何更改代码结构以避免需要此部分匹配。