20-CS-4003-001 Organization of Programming Languages Fall 2017
Control Structures

Lambda calculus, Type theory, Formal semantics, Program analysis

    Prev     Next     All lectures

Simple repetition

Java:
   
   import java.util.*;

   public class intro_4 {
      public static void main (String args[]) {
         Vector  p1 = new Vector  ();
         int i = Integer.parseInt(args[0]);
         while (i != 0) p1.add(i--);
         System.out.println(p1.toString());
      }
   }

The standard constructs for repetition are the for loop and the while. The above can be run as follows:

   prompt> java intro_4 10
   [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
However the following runs out of memory after trying to build an infinitely long list for 13 seconds:
   prompt> java intro_4 -1
   Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
           at java.util.Arrays.copyOf(Arrays.java:2760)
           at java.util.Arrays.copyOf(Arrays.java:2734)
           at java.util.Vector.ensureCapacityHelper(Vector.java:226)
           at java.util.Vector.add(Vector.java:728)
           at intro_4.main(intro_4.java:7)
   real    0m12.756s
   user    0m24.905s
   sys     0m0.392s
 
Haskell:
   f1 x = take x y where y = x : [ i-1 | i <- y]
The take x y in Haskell takes the first x tokens from the list y. The [ a | b <- c] construct is a "list comprehension" where c is a list, b becomes the first token taken from the list c, and a is an expression that evaluates to a value, usually depending on b, that is added to the list comprising the output. In this case the output list is y which is expressed as  x:[ i-1 | i <- y]. This means that input integer x is placed at the beginning of list y. The list comprehension in this example produces output tokens as long as y is not empty. In this example, y is initially [x] (the list of one token that happens to have the value of x). Then i becomes x and y becomes [x, x-1]. Next i becomes x-1 because y now has two elements and even though i <- y pulled the first element from y earlier, there is a new last element which is now pulled off and becomes the value of i. Observe that this process can continue forever. But the take only asks for x of the first tokens in the output list - the remaining ones are never generated because they are not asked for!!! This is an example of a fully lazy language and is different from Scheme or Java. However, both Scheme and Java can be modified to achieve the same effect as will be seen later.

Run this as follows:

   ghci> f1 10
   [10,9,8,7,6,5,4,3,2,1]
Try a negative number as input:
   ghci> f1(-1)
   []
which may not be considered fair because take is asked to take no tokens from the output so change the code to always take the first 10 tokens:
   f1 x = take 10 y  where  y = x : [ i-1 | i <- y]
and run it
   ghci> f1(-1)
   [-1,-2,-3,-4,-5,-6,-7,-8,-9,-10]
If take had not been present this code would have tried to display an infinitely long list of tokens, decreasing by 1.
 
Scheme:
   (define f1 (lambda (x) (if (= x 0) '() (cons x (f1 (- x 1))))))

Result:

   scheme> (f1 10)
   ;Value 11: (10 9 8 7 6 5 4 3 2 1)
2nd program:
   (define f2 (lambda (x) (if (= x 0) '() (append (f2 (- x 1)) (list x)))))

Result:

   scheme> (f2 10)
   ;Value 13: (1 2 3 4 5 6 7 8 9 10)

Scheme primarily depends on recursion for looping. The normal way to achieve this is to define a lambda expression, assigning a name to it, and using that name in the body of the lambda expression (although this is not necessary as will be seen later).

At this point an important question arises regarding stack space. One might think that stack overflows are possible when executing recursive functions such as the ones above. This is the case for both procedures above. Try (f1 -1) and (f2 -1). The result is

   ;Aborting!: maximum recursion depth exceeded
But, for example, f2 can be written as follows:
   (define f3 (lambda (x lst) (if (= x 0) lst (f3 (- x 1) (cons x lst)))))
which does not require that anything be stacked to get an answer because the "accumulated" list lst is passed as argument to f3. On (f3 -1 '()), though, we get:
   ;Aborting!: out of memory
   ;GC #18: took:   0.20  (20%) CPU time,   0.20  (19%) real time; free: 4191563
   ;GC #19: took:   0.30  (75%) CPU time,   0.20  (95%) real time; free: 4191625
simply because lst is growing in size from call to call of f3.