2

In functional languages such as Scheme or Lisp there exist for and for-all loops. However for loops require mutation since it's not a new stack frame each iteration. Since mutation is not available in these languages explicitly how do these functional languages implement their respective iterative loops?

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Artemis
  • 139
  • 12
  • 3
    *Since mutation is not available in these languages explicitly* : that part is false – coredump Mar 14 '18 at 07:49
  • in pure languages mutation is emulated by state passing, working in concert with looping being emulated by tail recursion. Prolog is one example. – Will Ness Mar 15 '18 at 16:29

2 Answers2

5

Scheme loops are implemented using recursion under the hood; constructs such as do are just macros that get translated to recursive procedures. For example, this loop in a typical procedural language:

void print(int n) {
    for (int i = 0; i < n; i++) {
        display(i);
    }
}

... Is equivalent to the following procedure in Scheme; here you can see that each part of the loop (initialization, exit condition, increment, body) has a corresponding expression:

(define (print n)
  (define (loop i)     ; helper procedure, a "named let" would be better
    (when (< i n)      ; exit condition, if this is false the recursion ends
      (display i)      ; body
      (loop (+ i 1)))) ; increment
  (loop 0))            ; initialization

Did you notice that there's nothing left to do after the recursion is called? the compiler is smart enough to optimize this to use a single stack frame, effectively making it as efficient as a for loop - read about tail recursion for more details. And just to clarify, in Scheme mutation is explicitly available, read about the set! instruction.

Óscar López
  • 232,561
  • 37
  • 312
  • 386
2

This question is really two questions, and a confusion.

Iteration in Scheme

In Scheme, iteration is implemented by recursion together with the semantics of the language mandating that certain kinds of recursion do not consume memory, in particular tail recursion. Note that this does not imply mutation. So, for instance, here is a definition of a while loop i Racket.

(define-syntax-rule (while test form ...)
  (let loop ([val test])
    (if val
        (begin
          form ...
          (loop test))
        (void))))

As you can see the recursive call to loop is in tail position and thus consumes no memory.

Iteration in traditional Lisps

Traditional Lisps do not mandate tail-call elimination and thus require iterative constructs: these are generally provided by the language, but usually can be implemented in terms of lower-level constructs, such as GO TO. Here is a definition of while in Common Lisp which does this:

(defmacro while (test &body forms)
  (let ((lname (make-symbol "LOOP")))
    `(tagbody
      ,lname
      (if ,test
          (progn
            ,@forms
            (go ,lname))))))

A confusion about mutation

Both Scheme and traditional Lisps provide mutation operators: neither are the pure functional languages that you may think they are. Scheme is closer to being one, but it still isn't very close.