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