-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy path02.08-Tail-Recursion.txt
161 lines (105 loc) · 7.5 KB
/
02.08-Tail-Recursion.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
2.8 Tail-Recursion
2.8 Хвостовая рекурсия
A recursive function is one that calls itself. Such a call is
tail-recursive if no work remains to be done in the calling function
afterwards. This function is not tail-recursive
Рекурсивными называют функции вызывающие сами себя. Хвостовой
рекурсией называется рекурсия, если в вызывающей не остается никакой
работы после вызова себя рекурсивно. Следующая функция не является
функцией с хвостовой рекурсией
(defun our-length (lst)
(if (null lst)
0
(1+ (our-length (cdr lst)))))
because on returning from the recursive call we have to pass the
result to 1+. The following function is tail-recursive, though
потому что вернувшись из рекурсивыного вызова мы должны пережать
результат в 1 +. Следующая функция является функцией с хвостовой
рекурсией
(defun our-find-if (fn lst)
(if (funcall fn (car lst))
(car lst)
(our-find-if fn (cdr lst))))
because the value of the recursive call is immediately returned.
потому что значение рекурсивного вызова сразу же возвращается.
Tail-recursion is desirable because many Common Lisp compilers can
transform tail-recursive functions into loops. With such a compiler,
you can have the elegance of recursion in your source code without the
overhead of function calls at runtime. The gain in speed is usually
great enough that programmers go out of their way to make functions
tail-recursive.
Хвостовая рекурсия более эффективна, потому что многие реализации
Common Lisp могут трасформировать хвостовую рекурсию в цикл. С такими
компиляторами вы можете получить элегантность рекурсии в исходном коде
без оверхеда на вызов функций. Прирост скорости обычно настолько
высок, что программисты делают многое чтобы сделать функцию с
хвостовой рекурсией.
A function which isn’t tail-recursive can often be transformed into
one that is by embedding in it a local function which uses an
accumulator. In this context, an accumulator is a parameter
representing the value computed so far. For example, our-length could
be transformed into
Функция, не обладающая свойством хвостовой рекурсии, часто может быть
трансформированна в таковую, при помощи включения в нее локальной
функции, использующей аккумулятор. В нашем контексте аккумулятор - это
параметр, представляющий значение, которое нужно вычислить в процессе
еркурсии. Например наша функция our-length может быть трансформирована
в
(defun our-length (lst)
(labels ((rec (lst acc) (if (null lst)
acc
(rec (cdr lst) (1+ acc)))))
(rec lst 0)))
where the number of list elements seen so far is contained in a second
parameter, acc. When the recursion reaches the end of the list, the
value of acc will be the total length, which can just be returned. By
accumulating the value as we go down the calling tree instead of
constructing it on the way back up, we can make rec tail-recursive.
где колличество элементов списка, которые будут пройдены содержится во
втором параметре acc. Когда рекурсивная функция достигнет конца
списка, значение acc будет его длиной, которое сразу может быть
возвращено. Аккумулируя значения при спуске по дереву вызовов вместо
того чтобы конструировать его поднимаясь мы можем сделать rec функцией
с хвостовой рекурсией.
Many Common Lisp compilers can do tail-recursion optimization, but not
all of them do it by default. So after writing your functions to be
tail-recursive, you may also want to put
Многие компиляторы Common Lisp делают оптимизацию хвостовой рекурсии,
но не все делают это по умолчанию. Так что после написания функции с
хвостовой рекурсией непомешает так же добавить
(proclaim '(optimize speed))
(proclaim '(optimize speed))
at the top of the file, to ensure that the compiler can take advantage
of your efforts. 2)
в начале файла, чтобы компилятор смог извлечь пользу из ваших
стараний. 2)
2) The declaration (optimize speed) ought to be an abbreviation for
(optimize (speed 3)). However, one Common Lisp implementation does
tail-recursion optimization with the former, but not the latter.
2) Декларация (optimize speed) является аббревиатурой для (optimize
(speed 3)). Тем не менее некоторые компиляторы Common Lisp делают
оптимизацию хвостовой рекурсии при первом варианте но не втором.
Given tail-recursion and type declarations, existing Common Lisp
compilers can generate code that runs as fast as, or faster than,
C. Richard Gabriel gives as ◦ an example the following function, which
returns the sum of the integers from 1 to n:
Используя хвостовую рекурсию и декларацию типов существующие
реализации компиляторов Common Lisp могут генерировать код такой же
бытрый, или даже быстрее кода на C. Ричард Гэбриэл приводит пример
следующей функции, которая возвращает сумму целых чисел от 1 до n:
(defun triangle (n)
(labels ((tri (c n)
(declare (type fixnum n c))
(if (zerop n)
c
(tri (the fixnum (+ n c)) (the fixnum (-n 1))))))
(tri 0 n)))
This is what fast Common Lisp code looks like. At first it may not seem
natural to write functions this way. It’s often a good idea to begin
by writing a function in whatever way seems most natural, and then, if
necessary, transforming it into a tail-recursive equivalent.
Это пример того, как выглядит быстрый код на Common Lisp. С первого
взгляда не кажется естественным писать код в таком виде. Хорошей идеей
будет написать функцию в том виде, в котором она выражается на лиспе
наиболее естественно, и затем, если возникнет необходимость,
трансформировать ее в вариант с хвостовой рекурсией.