问题描述
我正在努力理解下面代码的逻辑。我知道代码将返回从第一个到 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)
;如果它的尾部非空,则分别命名该 x
和 y
的头部和尾部”。换句话说,如果它是一个至少两个元素的列表,那么调用第一个元素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:
- 替换:
[1,0]
- 用
let (x₁ : y₁ : xs₁) = 1 : 0 : [] in x₁ + y₁ : (x₁ : y₁ : xs₁)
=let
、x₁
=1
、y₁
=0
评估xs₁
:{{1} }
- 将方程 #2 与
- 替换:
[]
- 用
1 + 0 : (1 : 0 : [])
=let (x₀ : y₀ : xs₀) = 1 : 1 : [0] in x₀ + y₀ : (x₀ : y₀ : xs₀)
、let
=x₀
、1
=y₀
评估1
:{{1} }
- 将方程 #2 与
- 列表的语法糖:
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-+---> [] |
| | | | | | | | | | | | | | | | | | | | | |
+-----+-----+--+--+-------+ +-----+--^--+--+--+-----------+ +-----+-^-+-----+ +-----+-^-+---+ +----+
| | | | | | |
+--+-----------------------+ | +--------------------------+-------------------+
| | |
+--------------------------+-----------------------------+
绑定是安全的,但请考虑如何更改代码结构以避免需要此部分匹配。