Functional programming 递归和尾部调用优化示例

Functional programming 递归和尾部调用优化示例,functional-programming,elixir,Functional Programming,Elixir,我试图学习长生不老药和函数编程,但在《长生不老药在行动》一书中,我很难理解这个例子 defmodule ListHelper do def sum([]), do: 0 def sum([head | tail]) do head + sum(tail) end end ListHelper.sum([12,3,4]) 它的返回值是19,但我不理解的是这些值是如何累积的 我以为头部会不断更新,然后当模式与[]匹配时,累积的头部会被添加到0中,并且函数会退出,但在玩了它之后

我试图学习长生不老药和函数编程,但在《长生不老药在行动》一书中,我很难理解这个例子

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

ListHelper.sum([12,3,4])
它的返回值是19,但我不理解的是这些值是如何累积的

我以为头部会不断更新,然后当模式与
[]
匹配时,累积的头部会被添加到
0
中,并且函数会退出,但在玩了它之后,我现在认为这不是发生的事情。有人能为这个例子中发生的事情提供另一种解释吗?如果我需要解释更多的话,我可以试着重新解释一下。

sum([head | tail])
head+sum(tail)
,所以
sum([12,3,4])
12+sum([3,4]),
sum([3,4])
3+sum([4])
<代码>总和([])
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在不断更新

没有。对
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
               +-----------------+
在命令行中:

~/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

仍然不清楚每次通话中如何追踪头部。下一次调用如何知道head之前的值是多少?@adamscott它不知道<代码>总和([3,4])为7。为了计算这个值,不需要知道
head
的“previous”值是12。12与
sum([3,4])
的值无关。好吧,我想我明白了,这是说12暂停执行数组尾部,3暂停,4暂停,然后因为我们找到了一个不调用递归的空白数组的模式,那么调用开始结束,我们加上0+4+3+12作为总数?@adamscott,基本上这是正确的。由于递归调用不在尾部位置,因此无法对尾部调用进行优化,因此它的处理方式与任何其他递归调用一样。
~/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