4 | 4 |
expressing the primitive recursive functions.
|
5 | 5 |
|
6 | 6 |
I'll assume you know what a primitive recursive function is. If not, go look
|
7 | |
it up, it's quite interesting, even if only for the fact that it demonstrates
|
8 | |
even a genius like Kurt Goedel can sometimes be mistaken. (He initially
|
|
7 |
it up, as it's quite interesting, if only for the fact that it demonstrates
|
|
8 |
even a genius like Kurt Gödel can sometimes be mistaken. (He initially
|
9 | 9 |
thought that all functions could be expressed primitive recursively, until
|
10 | 10 |
Ackermann came up with a counterexample.)
|
11 | 11 |
|
|
22 | 22 |
way. In PL-{GOTO}'s case, they just took PL and removed the `GOTO` construct.
|
23 | 23 |
The rest of the language essentially contains only `for` loops, so what you
|
24 | 24 |
get is something in which you can only express primitive recursive functions.
|
|
25 |
(That imperative programs consisting of only `for` loops can express only and
|
|
26 |
exactly the primitive recursive functions was established by Meyer and Ritchie
|
|
27 |
in "The complexity of loop programs".)
|
25 | 28 |
|
26 | 29 |
But what about functional languages?
|
27 | 30 |
|
|
32 | 35 |
|
33 | 36 |
Thing is, that's kind of difficult. Is it possible to take the same approach
|
34 | 37 |
PL-{GOTO} takes, and *syntactically* restrict a functional language to the
|
35 | |
primitive recursive functions? It *should* be possible... and easier than
|
36 | |
statically analyzing an arbitrary program... but it's not immediately trivial.
|
|
38 |
primitive recursive functions?
|
|
39 |
|
|
40 |
I mean, in a trivial sense, it must be; the original primitive recursive
|
|
41 |
formulae *were* functions. But they were highly arithmetical, with bounded
|
|
42 |
sums and products and whatnot. What would it look like in the setting of
|
|
43 |
general (and symbolic) functional programming?
|
|
44 |
|
37 | 45 |
Functional languages don't do the `for` loop thing, they do the recursion
|
38 | 46 |
thing, and there are no natural bounds on that recursion, so those would have
|
39 | 47 |
to be embodied by the grammar, and... well, it sounds interesting, but
|
|
77 | 85 |
### Note on Critical Arguments ###
|
78 | 86 |
|
79 | 87 |
I should note, though, that the second point is an oversimplification.
|
80 | |
Not *all* arguments need to be strictly "smaller" upon recursion -- only
|
|
88 |
Not *all* arguments need to be strictly "smaller" upon recursion — only
|
81 | 89 |
those arguments which are used to determine *if* the function recurses.
|
82 | 90 |
I'll call those the _critical arguments_. Other arguments can take on
|
83 | 91 |
any value (which is useful for having "accumulator" arguments and such.)
|
84 | 92 |
|
85 | 93 |
When statically analyzing a function for primitive recursive-ness, you
|
86 | |
need to check how it decides to rescurse, to find out which arguments are
|
|
94 |
need to check how it decides to recurse, to find out which arguments are
|
87 | 95 |
the critical arguments, so you can check that those ones always get
|
88 | 96 |
"smaller".
|
89 | 97 |
|
90 | |
But we can proceed in a simpler fashion here -- we can simply say that
|
|
98 |
But we can proceed in a simpler fashion here — we can simply say that
|
91 | 99 |
the first argument to every function is the critical argument, and all
|
92 | |
the rest aren't. I believe this is without loss of generality, as we can
|
93 | |
always split some functionality which would require more than one critical
|
|
100 |
the rest aren't. This is without loss of generality, as we can always
|
|
101 |
split some functionality which would require more than one critical
|
94 | 102 |
argument across multiple functions, each of which only has one critical
|
95 | 103 |
argument. (Much like every `for` loop has only one loop variable.)
|
96 | 104 |
|
|
102 | 110 |
find this syntax somewhat obnoxious, it is less obnoxious than requiring that
|
103 | 111 |
atoms are in ALL CAPS, which is what Exanoke originally had. In truth, there
|
104 | 112 |
would be no real problem with allowing atoms, parameters, and function names
|
105 | |
to all be arbitrarily alphanumeric, but it would require more static context
|
106 | |
checking to sort them all out, and we're trying to be heavily syntactic here.
|
|
113 |
(and even `self`) to all be arbitrarily alphanumeric, but it would require
|
|
114 |
more static context checking to sort them all out, and we're trying to be
|
|
115 |
as syntactic as reasonably possible here.
|
107 | 116 |
|
108 | 117 |
`:true` is the only truthy atom. Lists are by convention only, and, by
|
109 | 118 |
convention, lists compose via the second element of each pair, and `:nil` is
|
|
138 | 147 |
to allow the programmer to give it a better name, but more static context
|
139 | 148 |
checking would be involved.
|
140 | 149 |
|
141 | |
Note that `<if` does not seem to be truly necessary. Its only use is to embed
|
142 | |
a conditional into the first argument being passed to a recursive call. You
|
|
150 |
Note that `<if` is not truly necessary. Its only use is to embed a
|
|
151 |
conditional into the first argument being passed to a recursive call. You
|
143 | 152 |
could also use a regular `if` and make the recursive call in both branches,
|
144 | 153 |
one with `:true` as the first argument and the other with `:false`.
|
145 | 154 |
|
|
151 | 160 |
-> Functionality "Evaluate Exanoke program" is implemented by
|
152 | 161 |
-> shell command "script/exanoke %(test-file)"
|
153 | 162 |
|
154 | |
Basic examples.
|
|
163 |
`cons` can be used to make lists and trees and things.
|
155 | 164 |
|
156 | 165 |
| cons(:hi, :there)
|
157 | 166 |
= (:hi :there)
|
|
159 | 168 |
| cons(:hi, cons(:there, :nil))
|
160 | 169 |
= (:hi (:there :nil))
|
161 | 170 |
|
|
171 |
`head` extracts the first element of a cons cell.
|
|
172 |
|
162 | 173 |
| head(cons(:hi, :there))
|
163 | 174 |
= :hi
|
164 | 175 |
|
|
176 |
| head(:bar)
|
|
177 |
? head: Not a cons cell
|
|
178 |
|
|
179 |
`tail` extracts the second element of a cons cell.
|
|
180 |
|
165 | 181 |
| tail(cons(:hi, :there))
|
166 | 182 |
= :there
|
167 | 183 |
|
|
171 | 187 |
| tail(:foo)
|
172 | 188 |
? tail: Not a cons cell
|
173 | 189 |
|
174 | |
| head(:bar)
|
175 | |
? head: Not a cons cell
|
|
190 |
`<head` and `<tail` and syntactic variants of `head` and `tail` which
|
|
191 |
expect their argument to be "smaller than or equal in size to" a critical
|
|
192 |
argument.
|
176 | 193 |
|
177 | 194 |
| <head cons(:hi, :there)
|
178 | 195 |
? Expected <smaller>, found "cons"
|
|
180 | 197 |
| <tail :hi
|
181 | 198 |
? Expected <smaller>, found ":hi"
|
182 | 199 |
|
|
200 |
`if` is used for descision-making.
|
|
201 |
|
183 | 202 |
| if :true then :hi else :there
|
184 | 203 |
= :hi
|
185 | 204 |
|
186 | 205 |
| if :hi then :here else :there
|
187 | 206 |
= :there
|
188 | 207 |
|
|
208 |
`eq?` is used to compare atoms.
|
|
209 |
|
189 | 210 |
| eq?(:hi, :there)
|
190 | 211 |
= :false
|
191 | 212 |
|
|
197 | 218 |
| eq?(cons(:one, :nil), cons(:one, :nil))
|
198 | 219 |
= :false
|
199 | 220 |
|
|
221 |
`cons?` is used to detect cons cells.
|
|
222 |
|
200 | 223 |
| cons?(:hi)
|
201 | 224 |
= :false
|
202 | 225 |
|
203 | 226 |
| cons?(cons(:wagga, :nil))
|
204 | 227 |
= :true
|
205 | 228 |
|
|
229 |
`not` does the expected thing when regarding atoms as booleans.
|
|
230 |
|
206 | 231 |
| not(:true)
|
207 | 232 |
= :false
|
208 | 233 |
|
|
221 | 246 |
|
222 | 247 |
| self(:foo)
|
223 | 248 |
? Use of "self" outside of a function body
|
|
249 |
|
|
250 |
We can define functions. Here's the identity function.
|
224 | 251 |
|
225 | 252 |
| def id(#)
|
226 | 253 |
| #
|
227 | 254 |
| id(:woo)
|
228 | 255 |
= :woo
|
229 | 256 |
|
|
257 |
Functions must be called with the appropriate arity.
|
|
258 |
|
230 | 259 |
| def id(#)
|
231 | 260 |
| #
|
232 | 261 |
| id(:foo, :bar)
|
233 | 262 |
? Arity mismatch (expected 1, got 2)
|
234 | 263 |
|
|
264 |
| def snd(#, another)
|
|
265 |
| another
|
|
266 |
| snd(:foo)
|
|
267 |
? Arity mismatch (expected 2, got 1)
|
|
268 |
|
|
269 |
Parameter names must be defined in the function definition.
|
|
270 |
|
235 | 271 |
| def id(#)
|
236 | 272 |
| woo
|
237 | 273 |
| id(:woo)
|
238 | 274 |
? Undefined argument "woo"
|
239 | 275 |
|
|
276 |
You can't call a parameter as if it were a function.
|
|
277 |
|
240 | 278 |
| def wat(#, woo)
|
241 | 279 |
| woo(#)
|
242 | 280 |
| wat(:woo)
|
243 | 281 |
? Undefined function "woo"
|
|
282 |
|
|
283 |
You can't define two functions with the same name.
|
244 | 284 |
|
245 | 285 |
| def wat(#)
|
246 | 286 |
| :there
|
|
249 | 289 |
| wat(:woo)
|
250 | 290 |
? Function "wat" already defined
|
251 | 291 |
|
|
292 |
You can't name a function with an atom.
|
|
293 |
|
252 | 294 |
| def :wat(#)
|
253 | 295 |
| #
|
254 | 296 |
| :wat(:woo)
|
255 | 297 |
? Expected identifier, but found atom (':wat')
|
256 | 298 |
|
|
299 |
Every function takes at least one argument.
|
|
300 |
|
|
301 |
| def wat()
|
|
302 |
| :meow
|
|
303 |
| wat()
|
|
304 |
? Expected '#', but found ')'
|
|
305 |
|
|
306 |
The first argument of a function must be `#`.
|
|
307 |
|
257 | 308 |
| def wat(meow)
|
258 | 309 |
| meow
|
259 | 310 |
| wat(:woo)
|
260 | 311 |
? Expected '#', but found 'meow'
|
261 | 312 |
|
|
313 |
The subsequent arguments don't have to be called `#`, and in fact, they
|
|
314 |
shouldn't be.
|
|
315 |
|
262 | 316 |
| def snd(#, another)
|
263 | 317 |
| another
|
264 | 318 |
| snd(:foo, :bar)
|
265 | 319 |
= :bar
|
266 | 320 |
|
267 | |
| def snd(#, another)
|
268 | |
| another
|
269 | |
| snd(:foo)
|
270 | |
? Arity mismatch (expected 2, got 1)
|
|
321 |
| def snd(#, #)
|
|
322 |
| #
|
|
323 |
| snd(:foo, :bar)
|
|
324 |
? Expected identifier, but found goose egg ('#')
|
|
325 |
|
|
326 |
A function can call a built-in.
|
271 | 327 |
|
272 | 328 |
| def snoc(#, another)
|
273 | 329 |
| cons(another, #)
|
274 | 330 |
| snoc(:there, :hi)
|
275 | 331 |
= (:hi :there)
|
276 | |
|
277 | |
| def count(#)
|
278 | |
| self(<tail #)
|
279 | |
| count(cons(:alpha, cons(:beta, :nil)))
|
280 | |
? tail: Not a cons cell
|
281 | |
|
282 | |
| def count(#)
|
283 | |
| if eq?(#, :nil) then :nil else self(<tail #)
|
284 | |
| count(cons(:alpha, cons(:beta, :nil)))
|
285 | |
= :nil
|
286 | |
|
287 | |
| def last(#)
|
288 | |
| if not(cons?(#)) then # else self(<tail #)
|
289 | |
| last(cons(:alpha, cons(:beta, :graaap)))
|
290 | |
= :graaap
|
291 | |
|
292 | |
| def count(#, acc)
|
293 | |
| if eq?(#, :nil) then acc else self(<tail #, cons(:one, acc))
|
294 | |
| count(cons(:A, cons(:B, :nil)), :nil)
|
295 | |
= (:one (:one :nil))
|
296 | 332 |
|
297 | 333 |
Functions can call other user-defined functions.
|
298 | 334 |
|
|
322 | 358 |
| snocsnoc(:blarch, :glamch)
|
323 | 359 |
= (:blarch (:blarch :glamch))
|
324 | 360 |
|
|
361 |
A function may recursively call itself, as long as it does so with
|
|
362 |
values which are smaller than or equal in size to the critical argument
|
|
363 |
as the first argument.
|
|
364 |
|
|
365 |
| def count(#)
|
|
366 |
| self(<tail #)
|
|
367 |
| count(cons(:alpha, cons(:beta, :nil)))
|
|
368 |
? tail: Not a cons cell
|
|
369 |
|
|
370 |
| def count(#)
|
|
371 |
| if eq?(#, :nil) then :nil else self(<tail #)
|
|
372 |
| count(cons(:alpha, cons(:beta, :nil)))
|
|
373 |
= :nil
|
|
374 |
|
|
375 |
| def last(#)
|
|
376 |
| if not(cons?(#)) then # else self(<tail #)
|
|
377 |
| last(cons(:alpha, cons(:beta, :graaap)))
|
|
378 |
= :graaap
|
|
379 |
|
|
380 |
| def count(#, acc)
|
|
381 |
| if eq?(#, :nil) then acc else self(<tail #, cons(:one, acc))
|
|
382 |
| count(cons(:A, cons(:B, :nil)), :nil)
|
|
383 |
= (:one (:one :nil))
|
|
384 |
|
|
385 |
Arity must match when a function calls itself recursively.
|
|
386 |
|
325 | 387 |
| def urff(#)
|
326 | 388 |
| self(<tail #, <head #)
|
327 | 389 |
| urff(:woof)
|
|
332 | 394 |
| urff(:woof, :moo)
|
333 | 395 |
? Arity mismatch on self (expected 2, got 1)
|
334 | 396 |
|
|
397 |
The remaining tests demonstrate that a function cannot call itself if it
|
|
398 |
does not pass a values which is smaller than or equal in size to the
|
|
399 |
critical argument as the first argument.
|
|
400 |
|
335 | 401 |
| def urff(#)
|
336 | 402 |
| self(cons(#, #))
|
337 | 403 |
| urff(:woof)
|
|
377 | 443 |
| urff(cons(:graaap, :skooorp))
|
378 | 444 |
? tail: Not a cons cell
|
379 | 445 |
|
380 | |
Some practical examples, on Peano naturals. Addition:
|
|
446 |
Now, some practical examples, on Peano naturals. Addition:
|
381 | 447 |
|
382 | 448 |
| def inc(#)
|
383 | 449 |
| cons(:one, #)
|