forked from ds26gte/tyscheme
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rec.tex
284 lines (230 loc) · 7.69 KB
/
rec.tex
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
\chapter{Recursion}
\label{rec}
\index{recursion}
\index{procedure!recursive}
A procedure body can contain calls to other procedures,
not least itself:
\q{
(define factorial
(lambda (n)
(if (= n 0) 1
(* n (factorial (- n 1))))))
}
\n This {\em recursive} procedure calculates the {\em
factorial} of a number. If the number is \q{0}, the
answer is \q{1}. For any other number \q{n}, the
procedure uses itself to calculate the factorial of
\q{n - 1}, multiplies that subresult by \q{n}, and
returns the product.
Mutually recursive procedures are also possible. The
following predicates for evenness and oddness use each
other:
\q{
(define is-even?
(lambda (n)
(if (= n 0) #t
(is-odd? (- n 1)))))
(define is-odd?
(lambda (n)
(if (= n 0) #f
(is-even? (- n 1)))))
}
\index{even?@\q{even?}}
\index{odd?@\q{odd?}}
These definitions are offered here only as simple
illustrations of mutual recursion. Scheme already
provides the primitive predicates \q{even?} and
\q{odd?}.
\index{letrec@\q{letrec}}
\index{recursion!letrec@\q{letrec}}
\section{\q{letrec}}
If we wanted the above procedures as local
variables, we could try to use a \q{let} form:
\q{
(let ((local-even? (lambda (n)
(if (= n 0) #t
(local-odd? (- n 1)))))
(local-odd? (lambda (n)
(if (= n 0) #f
(local-even? (- n 1))))))
(list (local-even? 23) (local-odd? 23)))
}
\n This won’t quite work, because the occurrences of
\q{local-even?} and \q{local-odd?} in the initializations
don’t refer to the lexical variables themselves.
Changing the \q{let} to a \q{let*} won’t work either,
for while the \q{local-even?} inside \q{local-odd?}’s body
refers to the correct procedure value, the \q{local-odd?}
in \q{local-even?}’s body still points elsewhere.
To solve problems like this, Scheme provides the form
\q{letrec}:
\q{
(letrec ((local-even? (lambda (n)
(if (= n 0) #t
(local-odd? (- n 1)))))
(local-odd? (lambda (n)
(if (= n 0) #f
(local-even? (- n 1))))))
(list (local-even? 23) (local-odd? 23)))
}
\n The lexical variables introduced by a \q{letrec} are
visible not only in the \q{letrec}-body but also within
all the initializations. \q{letrec} is thus
tailor-made for defining recursive and mutually
recursive local procedures.
\index{named let@named \q{let}}
\index{let@\q{let}!named}
\section{Named \q{let}}
A recursive procedure defined using \q{letrec} can
describe loops. Let’s say we want to display a
countdown from \q{10}:
\q{
(letrec ((countdown (lambda (i)
(if (= i 0) 'liftoff
(begin
(display i)
(newline)
(countdown (- i 1)))))))
(countdown 10))
}
\n This outputs on the console the numbers \q{10} down to
\q{1}, and returns the result \q{liftoff}.
Scheme allows a variant of \q{let} called {\em named}
\q{let} to write this kind of loop more compactly:
\q{
(let countdown ((i 10))
(if (= i 0) 'liftoff
(begin
(display i)
(newline)
(countdown (- i 1)))))
}
\n Note the presence of a variable identifying the loop
immediately after the \q{let}. This program is
equivalent to the one written with \q{letrec}. You may
consider the named \q{let} to be a macro
(chapter~\ref{sugar}) expanding to the \q{letrec} form.
\index{iteration}
\index{loop}
\index{recursion!iteration as}
\index{tail recursion}
\index{recursion!tail}
\index{tail call}
\index{tail call!elimination of}
\index{procedure!tail-recursive}
\section{Iteration}
\q{countdown} defined above is really a recursive
procedure. Scheme can define loops only through
recursion. There are no special looping or iteration
constructs.
Nevertheless, the loop as defined above is a {\em
genuine} loop, in exactly the same way that other
languages bill their loops. I.e., Scheme takes special
care to ensure that recursion of the type used above
will not generate the procedure call/return overhead.
Scheme does this by a process called {\em tail-call
elimination}. If you look closely at the \q{countdown}
procedure, you will note that when the recursive call
occurs in \q{countdown}’s body, it is the {\em tail call},
or the very last thing done — each invocation of
\q{countdown} either does not call itself, or when it
does, it does so as its very last act. To a Scheme
implementation, this makes the recursion
indistinguishable from iteration. So go ahead, use
recursion to write loops. It’s safe.
Here’s another example of a useful tail-recursive
procedure:
\index{list-position@\q{list-position}}
\scmfilename listpos.scm
\scmdribble{
(define list-position
(lambda (o l)
(let loop ((i 0) (l l))
(if (null? l) #f
(if (eqv? (car l) o) i
(loop (+ i 1) (cdr l)))))))
}
\n \q{list-position} finds the index of the first
occurrence of the object \q{o} in the list \q{l}. If
the object is not found in the list, the procedure
returns \q{#f}.
Here’s yet another tail-recursive procedure, one that
reverses its argument list “in place”, i.e., by mutating
the contents of the existing list, and without
allocating a new one:
\index{reverse"!@\q{reverse"!}}
\scmfilename reverseb.scm
\scmdribble{
(define reverse!
(lambda (s)
(let loop ((s s) (r '()))
(if (null? s) r
(let ((d (cdr s)))
(set-cdr! s r)
(loop d s))))))
}
\n
(\q{reverse!} is a useful enough procedure that it is
provided primitively in many Scheme dialects, e.g.,
MzScheme and Guile.)
\index{Monte Carlo simulation}
In the last chapter, we defined two procedures \q{throw-one-die}
and \q{throw-two-dice} that returned the result of a single
experiment, i.e., a single throw of one or two dice. What if we
want to find the {\em expected} result, and we don't want to do the
calculations? One way, the so-called Monte Carlo method, is to
use iteration to
run the experiment many (e.g., 10000) times, and average the results.
\scmfilename montecarlo.scm
\scmdribble{
(define *num-trials* 10000)
(define monte-carlo
(lambda (experiment)
(let loop ((i 0) (acc 0.0))
(if (= i *num-trials*)
(/ acc *num-trials*)
(loop (+ i 1) (+ acc (experiment)))))))
}
\n Now run
\q{
(monte-carlo throw-one-die) |evalsto 3.4728
(monte-carlo throw-two-dice) |evalsto 6.9994
}
\n The expected values should be close to 3.5 and 7 respectively.
For some numerical-calculus examples of recursion (including iteration),
see appendix~\ref{numint}.
\index{map@\q{map}}
\index{for-each@\q{for-each}}
\section{Mapping a procedure across a list}
A special kind of iteration involves repeating the same
action for each element of a list. Scheme offers two
procedures for this situation: \q{map} and \q{for-each}.
The \q{map} procedure applies a given procedure to every
element of a given list, and returns the list of the
results. E.g.,
\q{
(map add2 '(1 2 3))
|evalsto (3 4 5)
}
The \q{for-each} procedure also applies a procedure to each
element in a list, but returns void. The procedure
application is done purely for any side-effects it may
cause. E.g.,
\q{
(for-each display
(list "one " "two " "buckle my shoe"))
}
\n has the side-effect of displaying the strings (in
the order they appear) on the console.
The procedures applied by \q{map} and \q{for-each}
need not be one-argument procedures. For example,
given an \q{n}-argument procedure, \q{map}
takes \q{n} lists and applies the procedure to
every set of \q{n} of arguments selected from across
the lists. E.g.,
\q{
(map cons '(1 2 3) '(10 20 30))
|evalsto ((1 . 10) (2 . 20) (3 . 30))
(map + '(1 2 3) '(10 20 30))
|evalsto (11 22 33)
}