【问题标题】:Recursion and Tail Call Optimization Example递归和尾调用优化示例
【发布时间】:2020-01-03 02:56:44
【问题描述】:

我正在尝试学习 Elixir 和函数编程,但无法理解 Elixir in Action 一书中的这个示例。

defmodule ListHelper do
  def sum([]), do: 0
  def sum([head | tail]) do
    head + sum(tail)
  end
end

ListHelper.sum([12,3,4])

this 的返回值为 19,但我不明白这些值是如何累积的。

我认为 head 正在不断更新,然后当模式与 [] 匹配时,累积的 head 将被添加到 0 并且该函数将退出,但在使用它之后我现在认为这不是什么继续。有人可以为这个例子中发生的事情提供另一种解释吗?如果我需要更多解释,我可以尝试重新审视这一点。

【问题讨论】:

    标签: functional-programming elixir


    【解决方案1】:

    sum([head | tail])head + sum(tail),所以 sum([12,3,4])12 + sum([3,4])sum([3,4])3 + sum([4])sum([4])4 + sum([])sum([])0,所以我们总共得到:

    sum([12,3,4]) = 12 + sum([3,4])
                  = 12 + 3 + sum([4])
                  = 12 + 3 + 4 + sum([])
                  = 12 + 3 + 4 + 0
                  = 19
    

    sum 的递归调用不是尾调用,因此这里不会发生尾调用优化。要实现 TCO,需要递归调用 sum 才能成为最后一个。

    defmodule ListHelper do
      def sum([], acc), do: acc
      def sum([head | tail], acc),
        do: sum(tail, acc + head)
    end
    
    ListHelper.sum([12,3,4], 0)
    #⇒ 19
    

    【讨论】:

    • 仍然对每次下一次呼叫时如何跟踪头部感到困惑。每次下一个调用如何知道之前的 head 值是什么?
    • @adamscott 它没有。 sum([3,4]) 是 7。为了计算它,不需要知道 head 的“先前”值是 12。12 与 sum([3,4]) 的值无关。
    • 好吧,我想我明白了,所以这是说 12 次暂停以执行数组尾部,3 次暂停,4 次暂停,然后因为我们点击了一个空白数组的模式不调用递归然后调用开始完成,我们添加 0 + 4 + 3 + 12 作为总数?
    • @adamscott,基本上这是正确的。由于递归调用不在尾部位置,因此不能对尾部调用进行优化,因此它的处理方式与任何其他递归调用一样。
    【解决方案2】:

    我以为 head 正在不断更新...

    不。每次调用sum() 时都会创建一个新的、单独的head 变量。这是通过创建一个新的堆栈框架来实现的,其中一个框架包含函数调用创建的所有局部变量——包括像head这样的参数变量。

    如果你这样写:

    x = 3 + func1()
    

    elixir 必须计算func1() 才能计算x 变量的值。并且,func1()的定义可能会创建自己的x变量,所以elixir会分配一个新的栈帧来计算func1()的返回值。一旦 elixir 计算出 func1() 的返回值,该值将代入上面的行:

               42
                |
                V
    
    x = 3 + func1()
    
    x = 3 + 42
    x = 45
    

    ...elixir 可以计算x 变量的值。

    如果你写,同样的事情会发生:

    4 + sum([5,6,7])
    

    唯一的区别是elixir会在栈上分配很多栈帧来计算sum([5,6,7])的返回值。使用递归函数调用,每个堆栈帧的返回值将取决于另一个堆栈帧的返回值。只有当达到基本情况,并且sum([])返回0时,elixir才能开始在每个堆栈帧内填充所需的值以计算返回值。

      4 + sum([5,6,7]) 
               |  
               |
         #1    V  sum([5,6,7])
       +--------------------+              
       | head = 5           |              
       | tail = [6,7]       |              
       |                    |             
       | return:            |            
       |   head + sum(tail) |                    
       |    5 +  sum([6,7]) |
       +-------------|------+
                     |
            #2       V  sum([6,7])
          +----------------------+   
          | head = 6             |    
          | tail = [7]           |   
          |                      |        
          | return:              |             
          |     head + sum(tail) |         
          |       6  + sum([7])  |
          +-----------------|----+
                            |        
                #3          V  sum([7])
              +----------------------+     
              | head = 7             |     
              | tail = []            |     
              |                      |     
              | return:              |     
              |     head + sum(tail) |     
              |       7  + sum([])   |     
              +----------------|-----+      
                               |
                    #4         V   sum([])
                 +-----------------------+
                 |  return: 0            |
                 +-----------------------+
    

    请注意,同时存在三个独立的head 变量。一旦底部堆栈帧返回,就会启动以下步骤:

     4 + sum([5,6,7]) 
               ^
               | 
               +---18-----------<----------+
       +--------------------+              |
       | head = 5           |              |
       | tail = [6,7]       |              |
       |                    |              ^
       | return:            |              |
       |   head + sum(tail) |              |      
       |    5 +  sum([6,7]) ---->---18-----+  #7
       +-------------^------+
                     |
                     +--13------<----------+
          +----------------------+         |
          | head = 6             |         |
          | tail = [7]           |         |
          |                      |         ^
          | return:              |         |     
          |     head + sum(tail) |         |
          |       6  + sum([7]) -|-->--13--+  #6
          +-----------------^----+
                            |        
                            +---7--<-------+
              +----------------------+     |
              | head = 7             |     ^
              | tail = []            |     |
              |                      |     |
              | return:              |     ^
              |     head + sum(tail) |     |
              |       7  + sum([]) --|>--7-+     
              +----------------^-----+      
                               |
                               +----0--<---+
                   +-----------------+     |
                   |  return: 0    --|>--0-+  #5
                   +-----------------+
    

    如果您在代码中添加一些打印语句,您可以看到这些步骤是如何执行的:

    defmodule My do
      def sum([]) do
        IO.puts("Inside sum([]):\n\treturning 0")
        0
      end
      def sum([head | tail]) do
        IO.puts("Inside sum([head|tail]), head=#{head} tail=#{inspect(tail, charlists: :as_lists)}")
        val = head + sum(tail)
        IO.puts("Inside sum([head|tail]), head=#{head} tail=#{inspect(tail, charlists: :as_lists)}")
        IO.puts("\treturning #{val}")
        val
      end
    end
    
    My.sum([5,6,7])
    

    在命令行:

    ~/elixir_programs$ elixir a.exs
    Inside sum([head|tail]), head=5 tail=[6, 7]
    Inside sum([head|tail]), head=6 tail=[7]
    Inside sum([head|tail]), head=7 tail=[]
    Inside sum([]):
        returning 0
    Inside sum([head|tail]), head=7 tail=[]
        returning 7
    Inside sum([head|tail]), head=6 tail=[7]
        returning 13
    Inside sum([head|tail]), head=5 tail=[6, 7]
        returning 18
    

    【讨论】:

      猜你喜欢
      • 2021-05-30
      • 2018-03-08
      • 1970-01-01
      • 1970-01-01
      • 2013-08-04
      • 1970-01-01
      • 2014-06-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多