20-CS-4003-001 Organization of Programming Languages Fall 2018
Recursion

Lambda calculus, Type theory, Formal semantics, Program analysis

Prev     Next     All lectures

Tail Recursion

 Scheme: ``` (define mult (lambda (l) (if (null? l) 1 (* (car l) (mult (cdr l)))))) (mult '(5 4 6 7 0 6 7)) (define mult^ (lambda (acc l) (if (null? l) acc (mult^ (* (car l) acc) (cdr l))))) (mult^ 1 '(5 4 6 7 7 8)) ``` Haskell: ``` multr l = foldr (\acc x -> acc*x) 1 l multl l = foldl (\acc x -> acc*x) 1 l mult l = if l == [] then 1 else (head l)*(mult (drop 1 l)) ``` Java: ``` public class mult { public static void main (String args[]) { int a[] = new int[]{ 1,2,3,4,5 }; int total = 1; try { for (int i=0 ; ; i++) total *= a[i]; } catch (Exception e) { } System.out.println(total); } } import java.util.*; public class A { public static int mult (Vector v) { if (v.isEmpty()) return 1; int n = v.get(0); v.removeElementAt(0); return n*mult(v); } public static void main (String args[]) { Vector v = new Vector (); v.add(2); v.add(3); v.add(4); v.add(5); System.out.println(mult(v)); } } import java.util.*; public class B { public static int mult (Vector v, int acc) { if (v.isEmpty()) return acc; int n = v.get(0); v.removeElementAt(0); return mult(v, n*acc); } public static void main (String args[]) { Vector v = new Vector (); v.add(2); v.add(3); v.add(4); v.add(5); System.out.println(mult(v, 1)); } } ``` - A recursive function is tail recursive if the final result of the recursive call is the final result of the function itself. If the result of the recursive call must be further processed (say, by adding 1 to it, or consing another element onto the beginning of it), it is not tail recursive. Formally, in Haskell, let a function be defined as f = t where f is a name and t is a lambda-term. Then f is tail recursive iff f occurs tail recursively in t and f occurs tail recursively in t iff f occurs in t and any of the following holds: t is variable; t is (\var -> t0) and f occurs tail recursively in t0; t is t0 t1 and f occurs tail recursively in t0 and does not occur in t1; t is let bs in t0 and f occurs tail recursively in t0 and for each binder var = t1 in bs, f does not occur in t1; t is case t0 of bs and f does not occur in t0 and for each branch b in bs, f does not occur or occurs tail recursively in b; The meaning of "occur in b" where b has form D vars -> t (where D is some data constructor and vars is a sequence of names), is \vars -> t instead of b (http://www.haskell.org/haskellwiki/Tail_recursion). The Scheme program mult is not tail recursive: the lambda expression representing t contains f but is not a variable (1.), t0 of (2.) is (g t1) and f is a member of t1 (so (2.) does not hold), cases (3.), (4.), and (5.) do not apply. Informally, in mult the multiplication (*) must wait for (mult (cdr l)) to return a value before it can be executed. Thus, the operation and the operand to multiply (namely (car l) must be placed on the stack. The Scheme program mult^ is tail recursive because (2.) holds from (1.). No stack is required to save anything, acc accumulates the result of all previous computations and sends it to the next recursion. Generally, the acc will contain current state, which may comprise much more information than just the desired result. For example, see this topological sort example. Haskell programs multr and multl both return the product of a list of numbers and they are both tail recursive. Program mult is not tail recursive. The Java program of class mult is not tail recursive because it is not recursive. The Java program of class A is not tail recursive because a recursion needs to be made before a multiplication can be executed. The Java program of class B is tail recursive.