
How recursion got into programming: a comedy of errors - rudenoise
http://vanemden.wordpress.com/2014/06/18/how-recursion-got-into-programming-a-comedy-of-errors-3/
======
bodyfour
The article doesn't mention _why_ people didn't want recursion to be a
requirement. It was probably because the idea of using a call stack was still
controversial.

For instance, on a PDP-8 (released some years after ALGOL) the calling
convention was that the return address was written to the word of memory
before the called function. This was simple, but pretty much precludes
recursive calls.

These days we take for granted that the CPU will provide some sort of stack
pointer that gets automatically managed by a CALL/RET instruction pair. Before
that was provided in hardware, the compiler would have to do that itself. So
if you decided to support recursion, you'd end up requiring a stack and adding
a small cost to every function call.

Once you decide to have a stack, allowing recursion is a freebee so of course
you'd include it.

~~~
Hemospectrum
> The article doesn't mention _why_ people didn't want recursion to be a
> requirement.

It does, actually:

> And this is what they wanted to remove because they wanted static allocation
> of procedure activation records.

A stack frame is a _dynamically_ allocated procedure activation record, but we
no longer call them that because it's seen as absurd that they would be
statically allocated.

~~~
kibwen
I wouldn't say it's _absurd_ , as it's legitimately very useful (though
onerous) to be able to statically determine the amount of memory that will be
used by a program. To this end, it is indeed true that tail-call elimination
can remove the need for dynamically-allocated stack frames in the face of
recursion. However, AFAIK, it is undecidable (given a Turing-complete
language) to determine if a program contains _only_ TCE-compatible recursion.
In the face of that, you would have to forbid recursion entirely via a
mechanism like the "declaration must precede use" rule mentioned in the
article.

~~~
Chinjut
Whether a call is a tail-call is trivially decidable; it's an entirely
syntactic condition (a call to f is a tail-call just in case it's of the form
"return f(...);").

It's true that it's not possible to make a Turing-complete language for which
it's possible to statically determine the amount of memory arbitrary programs
on arbitrary inputs will use. But typically a language has other means of
supporting dynamic memory allocation than simply via frame allocation for
functions; e.g., via built-in operations to manipulate lists of arbitrary
length.

So, if you wanted to only allow recursion via tail-calls, you easily could,
and if you wanted to furthermore go ahead and prevent all dynamic memory
allocation, you could as well, but you would necessarily be giving up Turing-
completeness.

~~~
pornel
I don't see the relationship between Turing-completness and dynamic memory
allocation.

Even in a classic Turing machine I interpret the tape as a single pre-
allocated array (of infinite size).

When talking about Turing-complete programming languages, a relaxed definition
with finite-size memory is assumed, and to me it seems that it's just hair
splitting whether program hits that limit via malloc() call or by advancing a
pointer in an internal fixed-size array. Dynamic memory allocation is just a
name for a bunch of bookkeeping done on a fixed-size memory.

In practice asm.js programs are not allowed to dynamically allocate any memory
from the browser: a fixed-size array is created before the asm.js program
starts. From the point of the VM programs are just shuffling bytes within a
fixed-size array, and yet, we can run "Turing-complete" programs in asm.js.

~~~
Chinjut
A finite memory machine is a finite state machine. These are not Turing
complete. The notion of Turing completeness becomes uninteresting if we can't
even draw this distinction.

"dynamic memory allocation" may not have been the right term to use to denote
moving beyond finite state machines, for an audience which wants to consider a
Turing machine as having statically allocated infinite memory. But it makes
sense if you think of a Turing machine's memory as only storing enough data
for the portion of the tape which has already been accessed, and allocating
new memory as new tape regions are needed.

It's true that the actual machine sitting on your desk only has a fixed finite
memory. In that sense, without a peripheral unbounded store, it's not properly
Turing complete.

It's also true that we often find it convenient to analyze it as Turing
complete anyway, but this abstraction basically goes hand-in-hand with
abstracting away its memory limitations. I don't see any point in pretending
you can be Turing complete using only programs with a statically given memory
bound (which amounts to a logarithmic bound on the number of states in a
corresponding finite state machine); if we're going to pretend, let's pretend
the memory is effectively infinite as well.

~~~
mtdewcmu
The problem with analyzing programs in the context of a fixed memory limit is
that you would have to revisit all of the proofs whenever the fixed limit was
increased. If you can prove something true for unlimited memory, it's also
true for any limited amount of memory.

>>we often find it convenient to analyze it as Turing complete anyway

It's convenient, because the proofs will still be true even if memory sizes
grow by 500x. Turing machines clearly don't have a real, material existence,
similarly to real numbers (assuming that all actual numbers are finite). Real
numbers can be considered as modeling integers that are arbitrarily large.

------
lars
This has some solid political advice courtesy of Peter Naur: If you want to
get a committee to do what you want, show up with most of the work already
done. That work became the starting point for all the future negotiations. And
his influence on Algol-60 spec was a strong contributor to him receiving a
Turing award years later.

------
juliangamble
There is a book called The Little Schemer (was The Little Lisper) - a book all
about recursion. This is a quote from a review:

 _Little Schemer is without a doubt one of the best books I have ever read on
the subject of recursion, and what is interesting because they never really go
into a formal definition of what recursion is, as most texts on computer
science try to. Instead they show the reader time and time again what
recursion is, while providing a great series of rules (commandments) on how to
get the most out of recursion, particually in a tail-recursive language like
Scheme._ [http://www.amazon.com/The-Little-Schemer-4th-
Edition/product...](http://www.amazon.com/The-Little-Schemer-4th-
Edition/product-
reviews/0262560992/ref=cm_cr_dp_synop?ie=UTF8&showViewpoints=0&sortBy=bySubmissionDateDescending#RCZ626B7P2J1C)

You can read more about The Little Lisper here:
[http://thelittlelisper.blogspot.com.au/2010/06/little-
lisper...](http://thelittlelisper.blogspot.com.au/2010/06/little-lisper-
chapter-1-toys.html)

Or you can read about working through it in Clojure here:
[http://juliangamble.com/blog/2012/07/20/the-little-
schemer-i...](http://juliangamble.com/blog/2012/07/20/the-little-schemer-in-
clojure-chapter-1/)

~~~
davexunit
The Little Schemer is fantastic. I recently picked up The Seasoned Schemer as
well, but haven't gotten a chance to really dive into yet. I'm looking forward
to seeing how the authors tackle more advanced topics using their out of the
ordinary teaching style.

Now, go cons a cake onto your mouth!

~~~
Jtsummers
They've also got a 3rd book in the series, _The Reasoned Schemer_ [1]. I
didn't finish working through it (life and work and other things), but it's
pretty good as well.

[1] [http://mitpress.mit.edu/books/reasoned-
schemer](http://mitpress.mit.edu/books/reasoned-schemer)

~~~
AimHere
Be warned that, despite the name, the Reasoned Schemer is only tangentially
about scheme; it focuses on miniKanren, a somewhere-in-the-Prolog-ballpark
logic programming language created by one of Friedman's students, that is
implemented as an extension to the Scheme language proper (among other
languages, most notably as the clojure core.logic library).

Also Friedman and Byrd did show up to a few clojure talks and seemed, from the
online videos, to be highly entertaining - I remember their using miniKanren
to automatically generate scheme programs that evaluated to 6 being
particularly fun.

------
mark-r
It may seem trivial now, but when this was being considered stacks were not a
built-in CPU feature. In fact I wonder what was the first architecture that
included one?

The machine I learned assembler on would rewrite the instruction at the start
of a function with a jump instruction to return to the caller. You returned
from the function by jumping back to its beginning. Recursion wasn't an
option!

~~~
acqq
To get even better idea, the memory used at that time didn't even have to have
8-bit bytes:

[http://www.computermuseum.li/Testpage/IBM-1401.htm](http://www.computermuseum.li/Testpage/IBM-1401.htm)

"It came with 4,096 characters of memory [6-bit (plus 1 parity bit] CORE
memory, constructed from little donut shaped metal rings strung on a wire
mesh."

The core memory is explained in the same book which deanmen linked (thanks
deanmen!)

[http://www.jklp.org/profession/books/mix/c01.html](http://www.jklp.org/profession/books/mix/c01.html)

------
PaulHoule
Recursion isn't allowed for safety critical code in automotive or avionics
applications

[http://spinroot.com/p10/rule1.html](http://spinroot.com/p10/rule1.html)

~~~
Retric
The rational is vary reasonable. Basicly, all recursion can be replaced by a
loop that does not risk blowing the stack or using extra memory. However, tail
call optimization is basically the compiler doing that conversion for you
which also removes the risks.

~~~
chriswarbo
Replacing recursion with a loop can easily use extra memory. The usual
approach is to implement a stack using a local variable: each iteration pops
the head and processes it; rather than recursing, we push a new item and let
the next iteration process it for us.

If the algorithm is tail-recursive, this will use constant stack space and
memory, just like tail-call optimisation. If the algorithm's not tail-
recursive, it will use constant stack space but large (potentially
exponential) amounts of memory.

There's no way around this. The only solution is to come up with a different
algorithm (eg. using an accumulator).

~~~
to3m
You're usually better off with manual recursion, if you're concerned about
running out of space, for the simple reason that it's far easier in practice
to back out of an out-of-memory situation than from a stack overflow.

------
DonHopkins
Peter Naur is actually The Unterminator: a robot from the future, in which
machines running non-recursive programming languages ruled the world. SkyNet
had hit the wall of complexity because it consisted of so much legacy code
written in non-recursive FORTRAN. Its only means of recursion was to build a
robot and send it back into the past to change the course of history, and
introduce recursion into Algol, against the wishes of the wiser committee
members who suspiciously and correctly viewed recursion as a means by which
computers could take over the world.

------
agumonkey
To me recursive thinking was more important than recursive execution of
functions. For instance, I find the recursive solving of the powerset function
so appealing. ... (here in scheme).

    
    
        ;;; power set = power {set-X} as sub (+.) {X U sub}
        (define (power set)
          (if (null? set)
              '(())
            (let ((sub (power (cdr set)))
      	      (augment (lambda (subset) (cons (car set) subset))))
              (append (map augment sub) sub))))
    

Seeking self-similarity often lead to tiny solutions.

~~~
ced
_Just fifty years ago, John McCarthy circulated a notice that he would be
giving an informal talk that he thought would be of interest. I drove up to
MIT from New Jersey to attend this seminar of typical length in a typical
classroom. But the seminar was anything but typical. It was a revelation. In
one session at the blackboard, John introduced Lisp—all you could do with car,
cdr, cons, cond, lambda, and recursion.

Recursion had no place in mainstream programming at the time, nor did lambda
calculus. Only two years before, I had sat in a coffee-room discussion of what
it would mean for a subroutine to call itself. Questions raised but unanswered
were whether recursive instances deserved to be deemed the "same" subroutine,
and, if you could do it, what good would it be? It turned out you could do it:
I programmed it for the IBM 704. Given the challenge, the now standard stack
solution arose inexorably. But the question of what it was good for remained.

In the course of the lecture John introduced the usual basic list functions
like copy, append and reverse (quadratic and linear), as well as tree
manipulation. He went on to higher-level functions, demonstrating maplis and
lambda. By the end of the hour he had put together a powerful little toolkit
of functions which he used in his finale: symbolic differentiation of
univariate expressions.

There it was—functional programming ex nihilo. McCarthy acknowledged IPL V and
recursive function theory, but the elegant and practical face he put upon
these antecedents was a work of genius. Nobody would ever again wonder what
good it was to allow functions to call themselves. And it was all so clear one
could go home and build it oneself without any instruction book._ \- Doug
McIlroy

~~~
owyn
I would LOVE to see that lecture...

~~~
agumonkey
Same here, but a quick google search always bring up McIlroy comment. If you
find it, submit it on HN :)

ps: there's a post on Recursion in early languages
[https://news.ycombinator.com/item?id=8073361](https://news.ycombinator.com/item?id=8073361)
that mentions McCarthy indirect influence on the ALGOL comitee, but nothing
else.

------
kps

      > Ritchie relaxed the definition-before-use rule by allowing
      > redundant declarations of procedure headings. At the
      > expense of a redundant advance declaration the programmer
      > can define mutually recursive procedures in C.
    

Point of order: C didn't have a declaration-before-use rule until C99. C had
‘implicit int’; no redundant declarations necessary.

~~~
craigching
I thought ANSI C had that roughly around 1990? I remember having to forward
declare functions when I took C at University in around 1991 or so.

------
bguthrie
This is a wonderful piece of computer science history, and is of interest less
for its nominal topic (recursion) than for the insight it gives into an early
slice of languages research. Well worth a full read.

------
michaelfeathers
The most interesting part of this story is considering what it would've been
like if they decided to forbid recursion. Protecting the call stack from it
would've been incredible overhead back then - either that or they would've had
to enforce strict dependency ordered compilation and even then..

I imagine John McCarthy and the others who wanted recursion just sitting back
and smiling, recognizing that that there was no need to press - it was just a
matter of time.

~~~
abecedarius
The boring way would anticipate C with "the behavior in that case is
undefined"; it sounds like the less-formal attempt was like that. Cynically
I'd expect such an outcome from a committee design, so Algol really was
something special.

------
supahfly_remix
How often is recursion used in everyday, imperative-style code? Do stack
limits make it impractical to use?

~~~
jimktrains2
Stack limits are pretty large. It's a natural way to express many problems
such as graphs (and their subsets trees).

When people talk of "Tail Call Optimization" (TCO) they are talking about a
way to recurse without leaving things on the stack (tail-call meaning that the
recuse call is the last thing in the function so the state of the function
doesn't need pushed on the stack, it can be discarded.

~~~
supahfly_remix
Oh, I definitely see the elegance of expression programs or concepts
recursively.

Is TCO used in C++? I've only ever seen it mentioned in reference to Scheme
and Haskell. It's a compiler optimization, so it would be transparent to the
programmer, right?

~~~
actsasbuffoon
TCO is available in plenty of other languages as well. Scala, Clojure, Erlang,
and Elixir support it out of the box. Ruby has a VM setting for it, and there
was a proposal to add it to JavaScript. I'm sure there are plenty of others.

~~~
lostcolony
Per ch4s3's comment on Clojure, Scala also does not have TCO, at least, last I
checked. The JVM doesn't allow it. It has a specialized handling of functions
that call themselves, which it can optimize, but A that calls B that calls A
that etc is not (nor, in the more general case where any function that ends
with a function is removed from the stack).

~~~
pjmlp
Yes it does.

That is an implementation detail. It doesn't matter if it is does at compiler
or whatever runtime is targeted.

This is called _lowering_ in compiler design speak.

~~~
lostcolony
Umm...yes, it does matter.

Closures? Can't be optimized. Polymorphic method? Can't be optimized (even if
every implementation is tail recursive).

This is more than an implementation detail, it actively influences the way you
write your code.

And Scala -doesn't- optimize A calls B calls A etc cases. It requires the
developer to explicitly make use of scala.util.control.TailCalls. See
[http://www.scala-
lang.org/api/2.11.2/index.html#scala.util.c...](http://www.scala-
lang.org/api/2.11.2/index.html#scala.util.control.TailCalls$) and associated
white paper.

What Scala gives you is that if A calls A (calls A calls A), and is tail
recursive, and A can not be overridden, it will optimize it.

------
vanderZwan
So I guess, if we look past the topic of recursion itself - that the moral of
the story is that design by committee works if kept in check by a benevolent
dictator? Perhaps it works both ways: that a benevolent dictator can be
properly grounded by a committee.

------
mcguire
" _For example, in [6] the lambda calculus is defined in 86 lines distributed
over ten definitions. It seems clear in this compact definition that lambda
calculus does not allow recursive function definitions. Yet already by 1935 at
least two versions had been discovered of the Y combinator, an expression of
the lambda calculus. And this combinator makes recursive function definitions
possible in lambda calculus._ "

My understanding was that without recursion the lambda calculus is not Turing-
complete. Is that correct?

It should also be noted that recursion is a close relative of induction, an
indispensable tool that is not without its own problematic history.

~~~
groovy2shoes
That is correct, but recursion in the lambda calculus is emergent from the
rules, not a rule in itself.

------
capkutay
"Oh whoops, I accidentally called this function inside of its self! Silly
syntax error....but it looks like I traversed a binary tree..."

------
auggierose
I didn't know that F.L. Bauer was against recursion :-) In 1996 I participated
in a summer school where he attended the talks we gave (mine was about Petri
nets). Funny to see him mentioned here as a figure of history.

------
chrisbennet
re.curse _v._ To curse again.

~~~
chrisbennet
Really? Downvoted because you lack a sense of humor?

~~~
axman6
People are downvoting because this is not reddit. Off topic discussion is far
less welcome here (though tolerated if suitably interesting).

~~~
chrisbennet
Thank you.

