简体   繁体   English

使用 Haskell 进行递归

[英]Recursion with Haskell

I am struggling to understand the logic of the code below.我正在努力理解下面代码的逻辑。 I know the code will return a list of Fibonacci numbers from the first till n th eg fib 3 will produce [2,1,1,0] .我知道代码将返回从第一个到第n个斐波那契数列的列表,例如fib 3将产生[2,1,1,0] I do not understand how ' n ' is split up in (x:y:xs) .我不明白 ' n ' 是如何在(x:y:xs)中拆分的。

I would appreciate any light on this.我将不胜感激。

Thanks谢谢

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

Your comment about "how" the code splits up the list returned by fib I cannot answer as I don't know all the internals of GHC.您关于代码“如何”拆分 fib 返回的列表的评论我无法回答,因为我不知道 GHC 的所有内部结构。 This process is called patter matching.这个过程称为模式匹配。 In Python and other languages you may be familiar with, this can be done在 Python 等你可能熟悉的语言中,可以这样做

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

Your function is of type您的 function 是类型

fib :: Int -> [Int]

so you can use pattern matching to extract the head, next head, and next tail of the list it returns, which is what happens in因此您可以使用模式匹配来提取它返回的列表的头部、下一个头部和下一个尾部,这就是在

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

Perhaps an area of confusion is where the list is being reconstructed so it can be appended to the rest of the list you are returning.也许一个令人困惑的地方是列表正在重建,因此它可以附加到您要返回的列表的 rest 中。 Your function can also be written like this你的function也可以这样写

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

I do not understand how n is split up in (x:y:xs) .我不明白如何在(x:y:xs)中拆分n

n is not being split; n没有被拆分; the list resulting from fib is being split. fib生成的列表正在拆分。

The original code:原代码:

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

Is equivalent to the following, with some of the syntactic sugar removed:相当于以下内容,去掉了一些语法糖:

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"

Since this code just destructures the list and then rebuilds an identical one, the case branch can also be written using an “as” pattern, eg: res@(x: y: xs) -> (x + y): res由于这段代码只是解构列表然后重建一个相同的列表,因此case分支也可以使用“as”模式编写,例如: res@(x: y: xs) -> (x + y): res

So fib is a function which takes one parameter n , which may be of any numeric type.所以fib是一个 function ,它接受一个参数n ,它可以是任何数字类型。 In the base case when n is 1, the code just returns a constant list [1, 0] .n为 1 的基本情况下,代码只返回一个常量列表[1, 0] In the recursive case, fib calls itself recursively, replacing n with n - 1 .在递归情况下, fib递归调用自身,将n替换为n - 1 The result will be a list, so the function then pattern-matches on that list to extract its components.结果将是一个列表,因此 function 然后对该列表进行模式匹配以提取其组件。

In the pattern x: y: xs , which is syntactic sugar for (:) x ((:) y xs) , the operator (:):: a -> [a] -> [a] is the data constructor for a list that is not empty, and x , y , and xs are variables;在模式x: y: xs中,它是(:) x ((:) y xs)的语法糖,运算符(:):: a -> [a] -> [a]是 a 的数据构造函数不为空的列表,并且xyxs是变量; so this is equivalent to saying “ if the input (the result of fib (n - 1) ) is non-empty, then name its head x ;所以这相当于说“如果输入( fib (n - 1)的结果)非空,则将其命名为x and if its tail is non-empty, then name the head and tail of that y and xs respectively”.如果它的尾巴不为空,则分别命名那个yxs的头和尾”。 In other words, if it's a list of at least two elements, then call the first element x , the second y , and the remainder xs (which may be empty).换句话说,如果它是一个至少包含两个元素的列表,则调用第一个元素x ,第二个y和其余的xs (可能为空)。

In fact it can be implemented in that explicit way, as you'd do in a language that lacks pattern-matching, by using guards or if expressions.事实上,它可以通过使用守卫或if表达式以这种显式方式实现,就像您在缺乏模式匹配的语言中所做的那样。 The result is quite unwieldy and error-prone, but it may be helpful as an illustration of how to mentally break it down:结果非常笨拙且容易出错,但它可能有助于说明如何在心理上将其分解:

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"

Obviously pattern matching is much simpler!显然模式匹配要简单得多!

So here's an example of how the original code would evaluate on the example input you gave, fib 3 :因此,这里有一个示例,说明原始代码如何根据您提供的示例输入fib 3进行评估:

  • evaluate: fib 3评估: fib 3
    • matches equation #2 with n₀ = 3 : let (x₀: y₀: xs₀) = fib (3 - 1) in x₀ + y₀: (x₀: y₀: xs₀)匹配方程 #2 与n₀ = 3 : let (x₀: y₀: xs₀) = fib (3 - 1) in x₀ + y₀: (x₀: y₀: xs₀)
    • evaluate: fib 2评估: fib 2
      • matches equation #2 with n₁ = 2 : let (x₁: y₁: xs₁) = fib (2 - 1) in x₁ + y₁: (x₁: y₁: xs₁)匹配方程 #2 与n₁ = 2 : let (x₁: y₁: xs₁) = fib (2 - 1) in x₁ + y₁: (x₁: y₁: xs₁)
      • evaluate: fib 1评估: fib 1
        • matches equation #1: [1, 0]匹配等式 #1: [1, 0]
      • substitute: let (x₁: y₁: xs₁) = 1: 0: [] in x₁ + y₁: (x₁: y₁: xs₁)替代: let (x₁: y₁: xs₁) = 1: 0: [] in x₁ + y₁: (x₁: y₁: xs₁)
      • evaluate let with x₁ = 1 , y₁ = 0 , xs₁ = [] : 1 + 0: (1: 0: [])xs₁ = 1 , y₁ = 0 , x₁ = [] : 1 + 0: (1: 0: [])评估let
    • substitute: let (x₀: y₀: xs₀) = 1: 1: [0] in x₀ + y₀: (x₀: y₀: xs₀)替代: let (x₀: y₀: xs₀) = 1: 1: [0] in x₀ + y₀: (x₀: y₀: xs₀)
    • evaluate let with x₀ = 1 , y₀ = 1 , xs₀ = [0] : 1 + 1: (1: 1: [0])y₀ = 1 xs₀ 1 x₀ [0] : 1 + 1: (1: 1: [0])评估let
  • syntactic sugar for lists: [2, 1, 1, 0]列表的语法糖: [2, 1, 1, 0]

And a diagram, showing how it builds up a list where each element's value refers to the subsequent two elements:还有一张图表,展示了它如何构建一个列表,其中每个元素的值都引用了随后的两个元素:

          ┌─────┬───────────┬───────┐ ┌─────┬───────────┬───────────┐ ┌─────┬───┬─────┐ ┌─────┬───┬───┐ ┌────┐
… fib 3───▶ (:) │ (+) x₁ y₁ │ fib 2─┼─▶ (:) │ (+) x₀ y₀ │ xs₁/fib 1─┼─▶ (:) │ 1 │ xs₀─┼─▶ (:) │ 0 │ ○─┼─▶ [] │
          └─────┴─────┼──┼──┴───────┘ └─────┴──▲──┼──┼──┴───────────┘ └─────┴─▲─┴─────┘ └─────┴─▲─┴───┘ └────┘
                      └──┼─────────────────────┘  │  └────────────────────────┼─────────────────┘
                         └────────────────────────┴───────────────────────────┘

ASCII version: ASCII 版本:

          +-----+-----------+-------+   +-----+-----------+-----------+   +-----+---+-----+   +-----+---+---+   +----+
          |     |           |       |   |     |           |           |   |     |   |     |   |     |   |   |   |    |
… fib 3---> (:) | (+) x1 y1 | fib 2-+---> (:) | (+) x0 y0 | xs1/fib 1-+---> (:) | 1 | xs0-+---> (:) | 0 | o-+---> [] |
          |     |     |  |  |       |   |     |     |  |  |           |   |     |   |     |   |     |   |   |   |    |
          +-----+-----+--+--+-------+   +-----+--^--+--+--+-----------+   +-----+-^-+-----+   +-----+-^-+---+   +----+
                      |  |                       |  |  |                          |                   |
                      +--+-----------------------+  |  +--------------------------+-------------------+
                         |                          |                             |
                         +--------------------------+-----------------------------+

Notice the calls to error : if you try to evaluate a pattern match that isn't exhaustive, it will throw an exception.请注意对error的调用:如果您尝试评估不详尽的模式匹配,它将引发异常。 In this case, fib will always have at least two elements, so the let binding is safe, but consider how you could change the structure of the code to avoid needing this partial match.在这种情况下, fib总是至少有两个元素,所以let绑定是安全的,但是考虑如何更改代码的结构以避免需要这种部分匹配。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM