You are on page 1of 16

www.jntuworld.com www.jwjobs.

net

www.jntuworldupdates.org

Unit- IV Control Flow

UNIT IV- CONTROL FLOW


Ordering is fundamental to most (though not all) models of computing. It
determines what should be done first, what second, and so forth, to accomplish
some desired task. the language mechanisms used to specify ordering into seven
principal categories.
Constructs for specifying the execution order:
1. Sequencing: the execution of statements and evaluation of expressions is
usually in the order in which they appear in a program text
2. Selection (or alternation): a run-time condition determines the choice among
two or more statements or expressions
3. Iteration: a statement is repeated a number of times or until a run-time condition
is met
4. Procedural abstraction: subroutines encapsulate collections of statements and
subroutine calls can be treated as single statements
5. Recursion: subroutines which call themselves directly or indirectly to solve a
problem, where the problem is typically defined in terms of simpler versions of
itself
6. Concurrency: two or more program fragments executed in parallel, either on
separate processors or interleaved on a single processor
7. Nondeterminacy: the execution order among alternative constructs is
deliberately left unspecified, indicating that any alternative will lead to a correct
result

1) Expression Evaluation
An expression consists of
An atomic object, e.g. number or variable
An operator applied to a collection of operands (or arguments) that are
expressions
Common syntactic forms for operators:
Function call notation, e.g. somefunc(A, B, C)
Infix notation for binary operators, e.g. A + B
Prefix notation for unary operators, e.g. -A
Postfix notation for unary operators, e.g. i++
Cambridge Polish notation, e.g. (* (+ 1 3) 2) in Lisp
"Multi-word" infix, e.g. a>b?a:b in C and
myBox displayOn: myScreen at: 100@50
in Smalltalk, where displayOn: and at: are written infix with
arguments mybox, myScreen, and 100@50
www.specworld.in 1 www.smartzworld.com

www.jntuworld.com Page 1

www.jntuworld.com
www.jntuworld.com www.jwjobs.net

www.jntuworldupdates.org

Unit- IV Control Flow

Operator Precedence and Associativity

Precedence rules specify that certain operators, in the absence of


parentheses, group “more tightly” than other operators. Associativity rules specify
that quences of operators of equal precedence group to the right or to the left

• Precedence, associativity

– C has 15 levels - too many to remember

– Pascal has 3 levels - too few for good semantics

– Fortran has 8

– Ada has 6

• Ada puts and & or at same level

– Lesson: when unsure, use parentheses!

Associativity rules are somewhat more uniform across languages, but still
dis-play some variety. The basic arithmetic operators almost always associate left-
toright, so 9 - 3 - 2 is 4 and not 8. In Fortran, as noted above, the exponentiation
operator (**) follows standard mathematical convention and associates right-to-
left, so 4**3**2 is 262144 and not 4096. In Ada, exponentiation does not
associate: one must write either (4**3)**2 or 4**(3**2); the language syntax does
not allow the unparenthesized form.

The use of infix, prefix, and postfix notation sometimes lead to ambiguity as to
what is an operand of what
Fortran example: a+b*c**d**e/f
Operator precedence: higher operator precedence means that a (collection of)
operator(s) group more tightly in an expression than operators of lower precedence
Operator associativity: determines grouping of operators of the same
precedence
Left associative: operators are grouped left-to-right (most common)
Right associative: operators are grouped right-to-left (Fortran power operator **,
C assignment operator = and unary minus)
Non-associative: requires parenthesis when composed (Ada power operator **)
www.specworld.in
Pascal's flat precedence levels is a design2 mistake www.smartzworld.com

www.jntuworld.com Page 2

www.jntuworld.com
www.jntuworld.com www.jwjobs.net

www.jntuworldupdates.org

Unit- IV Control Flow

if A<B and C<D then


is grouped as follows
if A<(B and C)<D then
Note: levels of operator precedence and associativity are
easily captured in a grammar as we saw earlier

Evaluation Order of Expressions


Precedence and associativity state the rules for grouping operators in
expressions, but do not determine the operand evaluation order!
Expression
a-f(b)-b*c
is structured as
(a-f(b))-(b*c)
but either (a-f(b)) or (b*c) can be evaluated first
The evaluation order of arguments in function and subroutine calls may differ,
e.g. arguments evaluated from left to right or right to left
Knowing the operand evaluation order is important
Side effects: suppose f(b) above modifies the value of b (f(b) has a “side
effect”) then the value will depend on the operand evaluation order
Code improvement: compilers rearrange expressions to maximize efficiency,
e.g. a compiler can improve memory load efficiency by moving loads up in the
instruction stream
• Side Effects
– often discussed in the context of functions
– a side effect is some permanent state change caused by execution of
function
• some noticable effect of call other than return value
• in a more general sense, assignment statements provide the
ultimate example of side effects
– they change the value of a variable
• Several languages outlaw side effects for functions
– easier to prove things about programs
– closer to Mathematical intuition
– easier to optimize
– (often) easier to understand
• But side effects can be nice
– consider rand()
• Side effects are a particular problem if they affect state used in other parts of
the expression in which a function call appears
www.specworld.in – It's nice not to specify an order,
3 because it makes it easier www.smartzworld.com
to optimize

www.jntuworld.com Page 3

www.jntuworld.com
www.jntuworld.com www.jwjobs.net

www.jntuworldupdates.org

Unit- IV Control Flow

– Fortran says it's OK to have side effects


• they aren't allowed to change other parts of the expression
containing the function call
• Unfortunately, compilers can't check this completely, and most
don't at all

Expression Operand Reordering Issues


Rearranging expressions may lead to arithmetic overflow or different floating
point results
Assume b, d, and c are very large positive integers, then if b-c+d is rearranged
into (b+d)-c arithmetic overflow occurs
Floating point value of b-c+d may differ from b+d-c
Most programming languages will not rearrange expressions when parenthesis
are used, e.g. write (b-c)+d to avoid problems
Design choices:
Java: expressions evaluation is always left to right in the order operands are
provided in the source text and overflow is always detected
Pascal: expression evaluation is unspecified and overflows are always
detected
C anc C++: expression evaluation is unspecified and overflow detection is
implementation dependent
Lisp: no limit on number representation

Short-Circuit Evaluation
Short-circuit evaluation of Boolean expressions: the result of an operator can be
determined from the evaluation of just one operand
Pascal does not use short-circuit evaluation
The program fragment below has the problem that element a[11] is read
resulting in a dynamic semantic error:
var a:array [1..10] of integer;
...
i := 1;
while i<=10 and a[i]<>0 do
i := i+1
C, C++, and Java use short-circuit conditional and/or operators
If a in a&&b evaluates to false, b is not evaluated
If a in a||b evaluates to true, b is not evaluated
Avoids the Pascal problem, e.g. while (i <= 10 && a[i] != 0) ...
Ada uses and then and or else, e.g. cond1 and then cond2
www.specworld.in
Ada, C, and C++ also have regular bit-wise4 Boolean operators www.smartzworld.com

www.jntuworld.com Page 4

www.jntuworld.com
www.jntuworld.com www.jwjobs.net

www.jntuworldupdates.org

Unit- IV Control Flow

Assignments and Expressions

Fundamental difference between imperative and functional languages


Imperative: "computing by means of side effects”
Computation is an ordered series of changes to values of variables in memory
(state) and statement ordering is influenced by run-time testing values of variables
Expressions in functional language are referentially transparent:
All values used and produced depend on the local referencing environment of
the expression
A function is idempotent in a functional language: it always returns the same
value given the same arguments because of the absence of side-effects L-Values
vs. R-Values and Value

Model vs. Reference Model


Consider the assignment of the form: a := b
The left-hand side a of the assignment is an l-value which is an expression that
should denote a location, e.g. array element a[2] or a variable foo or a dereferenced
pointer *p
The right-hand side b of the assignment is an r-value which can be any
syntactically valid expression with a type that is compatible to the lefthand side
Languages that adopt the value model of variables copy the value of b into the
location of a (e.g. Ada, Pascal, C)
Languages that adopt the reference model of variables copy references, resulting
in shared data values via multiple references
Clu copies the reference of b into a so that a and b refer to the same object
Java is a mix: it uses the value model for built-in types and the reference model
for class instances

Special Cases of Assignments


Assignment by variable initialization
Use of uninitialized variable is source of many problems, sometimes compilers
are able to detect this but with programmer involvement e.g. definite assignment
requirement in Java
Implicit initialization, e.g. 0 or NaN (not a number) is assigned by default when
variable is declared
Combinations of assignment operators
In C/C++ a+=b is equivalent to a=a+b (but a[i++]+=b is different from
a[i++]=a[i++]+b, ouch!)
Compiler produces better code, because the address of a variable is only
www.specworld.in
calculated once 5 www.smartzworld.com

www.jntuworld.com Page 5

www.jntuworld.com
www.jntuworld.com www.jwjobs.net

www.jntuworldupdates.org

Unit- IV Control Flow

Multiway assignments in Clu, ML, and Perl


a,b := c,d assigns c to a and d to b simultaneously, e.g. a,b := b,a swaps a with
b
a,b := 1 assigns 1 to both a and b

2) Structured and Unstructuted Flow


Unstructured flow: the use of goto statements and statement labels to implement
control flow
Merit or evil?
Generally considered bad, but sometimes useful for jumping out of nested loops
and for coding the flow of exceptions (when a language does not support exception
handling)
Java has no goto statement (supports labeled loops and breaks)
Structured flow:
Statement sequencing
Selection with “if-then-else” statements and “switch” statements
Iteration with “for” and “while” loop statements
Subroutine calls (including recursion)
All of which promotes “structured programming”.

Structured Alternatives to goto


Once the principal structured constructs had been defined, most of the controversy
surrounding gotos revolved around a small number of special cases, each of which
was eventually addressed in structured ways
Mid-loop exit and continue: A common use of gotos in Pascal was to break out
of the middle of a loop:

while not eof do begin


readln(line);
if all_blanks(line) then goto 100;
consume_line(line)
end;
100: _

mid-loop exits are supported by special “one-and-a half” loop constructs in


languages like Modula, C, and Ada. Some languages also provide a statement to
skip the remainder of the current loop iteration: continue in C; cycle in Fortran 90;
next in Perl.

www.specworld.in 6 www.smartzworld.com

www.jntuworld.com Page 6

www.jntuworld.com
www.jntuworld.com www.jwjobs.net

www.jntuworldupdates.org

Unit- IV Control Flow

Early returns from subroutines: Gotos were used fairly often in Pascal to termi-
nate the current subroutine:

procedure consume_line(var line: string);


...
begin
...
if line[i] = ’%’ then goto 100;
(* rest of line is a comment *)
...
100:
end;

Multilevel returns: Returns and (local) gotos allow control to return from the
current subroutine. On occasion it maymake sense to return from a surrounding
routine.
In the event of a nonlocal goto, the language implementation must guarantee
to repair the run-time stack of subroutine call information. This repair
operation is known as unwinding. It requires not only that the implementation
deallocate the stack frames of any subroutines from which we have escaped,
but also that it perform any bookkeeping operations, such as restoration of
register contents, that would have been performed when returning from those
routines.
As a more structured alternative to the nonlocal goto, Common Lisp
provides a return-from statement that names the lexically surrounding function or
block from which to return, and also supplies a return value

Errors and other exceptions: The notion of a multilevel return assumes that the
callee knows what the caller expects, and can return an appropriate value. In a
related and arguably more common situation, a deeply nested block or subroutine
may discover that it is unable to proceed with its usual function and, moreover,
lacks the contextual information it would need to recover in any graceful way. The
only recourse in such a situation is to “back out” of the nested context to some
point in the program that is able to recover. Conditions that require a program to
“back out” are usually called exceptions

Continuations
The notion of nonlocal gotos that unwind the stack can be generalized by
defining what are known as continuations. In low-level terms, a continuation
www.specworld.in 7
consists of a code address and a referencing www.smartzworld.com
environment to be restored when

www.jntuworld.com Page 7

www.jntuworld.com
www.jntuworld.com www.jwjobs.net

www.jntuworldupdates.org

Unit- IV Control Flow

jumping to that address. In higher-level terms, a continuation is an abstraction that


captures a context in which execution might continue. Continuations are
fundamental to denotational semantics. They also appear as first-class values in
certain languages (notably Scheme and Ruby), allowing the programmer to define
new controlflow Constructs

3) Sequencing

– specifies a linear ordering on statements one statement follows


another
– very imperative, Von-Neuman
A list of statements in a program text is executed in topdown
order
A compound statement is a delimited list of statements
A compund statement is called a block when it includes variable declarations
C, C++, and Java use { and } to delimit a block
Pascal and Modula use begin ... end
Ada uses declare ... begin ... end
Special cases: in C, C++, and Java expressions can be inserted as statements
In pure functional languages sequencing is impossible (and not desired!)

4) Selection

If-then-else selection statements in C and C++:


if (<expr>) <stmt> [else <stmt>]
Condition is a bool, integer, or pointer
Grouping with { and } is required for statement sequences in the then clause
and else clause
Syntax ambiguity is resolved with “an else matches the closest if” rule
Conditional expressions, e.g. if and cond in Lisp and a?b:c in C
Java syntax is like C/C++, but condition must be Boolean
Ada syntax supports multiple elsif's to define nested conditions:
if <cond> then
<statements>
elsif <cond> then
...
else
<statements>
end if
www.specworld.in 8 www.smartzworld.com

www.jntuworld.com Page 8

www.jntuworld.com
www.jntuworld.com www.jwjobs.net

www.jntuworldupdates.org

Unit- IV Control Flow

Case/switch statements are different from if-then-else statements in that an


expression can be tested against multiple constants to select statement(s) in one
of the arms of the case statement:
C, C++, and Java:
switch (<expr>)
{ case <const>: <statements> break;
case <const>: <statements> break;
...
default: <statements>
}
A break is necessary to transfer control at the end of an arm to the end of the
switch statement
Most programming languages support a switch-like statement, but do not
require the use of a break in each arm
A switch statement is much more efficient compared to nested ifthen- else
statements
o Fortran computed gotos
o jump code
 for selection and logically-controlled loops
 no point in computing a Boolean value into a register, then
testing it
 instead of passing register containing Boolean out of
expression as a synthesized attribute, pass inherited
attributes INTO expression indicating where to jump to if
true, and where to jump to if false

Short-Circuited Conditions

Code generated w/o short-circuiting (Pascal)

if ((A > B) and (C > D)) or (E _= F) then


then clause
else
else clause
In Pascal, which does not use short-circuit evaluation, the output code would
look something like this.
r1 := A –– load
r2 := B
www.specworld.in
r1 := r1 > r2 9 www.smartzworld.com

www.jntuworld.com Page 9

www.jntuworld.com
www.jntuworld.com www.jwjobs.net

www.jntuworldupdates.org

Unit- IV Control Flow

r2 := C
r3 := D
r2 := r2 > r3
r1 := r1 & r2
r2 := E
r3 := F
r2 := r2 _= r3
r1 := r1 | r2
if r1 = 0 goto L2
L1: then clause –– (label not actually used)
goto L3
L2: else clause
L3:
The root of the subtree for ((A > B) and (C > D)) or (E _= F) would name r1
as the register containing the expression value.

Code generated w/ short-circuiting (C)


In jump code, by contrast, the inherited attributes of the condition’s
root Code generation for short-circuiting would indicate that control
should “fall through” to L1 if the condition is true,or branch to L2 if
the condition is false. Output code would then look something like
this:
r1 := A
r2 := B
if r1 <= r2 goto L4
r1 := C
r2 := D
if r1 > r2 goto L1
L4: r1 := E
r2 := F
if r1 = r2 goto L2
L1: then clause
goto L3
L2: else clause
L3:

www.specworld.in 10 www.smartzworld.com

www.jntuworld.com Page 10

www.jntuworld.com
www.jntuworld.com www.jwjobs.net

www.jntuworldupdates.org

Unit- IV Control Flow

5) Iteration

Enumeration-controlled loops repeat a collection of statements a number of


times, where in each iteration a loop index variable takes the next value of a set of
values specified at the beginning of the loop
Logically-controlled loops repeat a collection of statements until some Boolean
condition changes value in the loop
Pretest loops test condition at the begin of each iteration
Posttest loops test condition at the end of each iteration
Midtest loops allow structured exits from within loop with exit Conditions

Enumeration-Controlled Loops
History of failures on design of enumeration-controlled loops
Fortran-IV:
DO 20 i = 1, 10, 2
...
20 CONTINUE
which is defined to be equivalent to
i=1
20 ...
i=i+2
IF i.LE.10 GOTO 20
Problems:
Requires positive constant loop bounds (1 and 10) and step size (2)
If loop index variable i is modified in the loop body, the number of
iterations is changed compared to the iterations set by the loop bounds
GOTOs can jump out of the loop and also from outside into the loop
The value of counter i after the loop is implementation dependent
The body of the loop will be executed at least once (no empty bounds)

Fortran-77:
Same syntax as in Fortran-IV, but many dialects support ENDDO instead of
CONTINUE statements
Can jump out of the loop, but cannot jump from outside into the loop
Assignments to counter i in loop body are not allowed
Number of iterations is determined by max((H-L+S)/S, 0) for lower bound L,
upper bound H, step size S
Body is not executed when (H-L+S)/S < 0
www.specworld.in 11
Either integer-valued or real-valued expressions for loop bounds andwww.smartzworld.com
step sizes
www.jntuworld.com Page 11

www.jntuworld.com
www.jntuworld.com www.jwjobs.net

www.jntuworldupdates.org

Unit- IV Control Flow

Changes to the variables used in the bounds do not affect the number of
iterations executed
Terminal value of loop index variable is the most recent value assigned, which is
L + S*max((H-L+S)/S, 0)
C, C++, and Java do not have true enumeration-controlled loops
A “for” loop is essentially a logically-controlled loop
for (i = 1; i <= n; i++) ...
which iterates i from 1 to n by testing i <= n before the start of each iteration and
updating i by 1 in each iteration
Why is this not enumeration controlled?
Assignments to counter i and variables in the bounds are allowed, thus it is the
programmer's responsibility to structure the loop to mimic enumeration loops
Use continue to jump to next iteration
Use break to exit loop
C++ and Java also support local scoping for counter variable
for (int i = 1; i <= n; i++) ...

Other problems with C/C++ for loops to emulate enumerationcontrolled loops are
related to the mishandling of bounds and limits of value representations
This C program never terminates (do you see why?)
#include <limits.h> // INT_MAX is max int value
main()
{ int i;
for (i = 0; i <= INT_MAX; i++)
printf(“Iteration %d\n”, i);
}
This C program does not count from 0.0 to 10.0, why?
main()
{ float n;
for (n = 0.0; n <= 10; n += 0.01)
printf(“Iteration %g\n”, n);
}

How is loop iteration counter overflow handled?


C, C++, and Java: nope
Fortran-77
Calculate the number of iterations in advance
For REAL typed index variables an exception is raised when overflow occurs
Pascal and Ada
www.specworld.in 12
Only specify step size 1 and -1 and detection www.smartzworld.com
of the end of the iterations is safe
www.jntuworld.com Page 12

www.jntuworld.com
www.jntuworld.com www.jwjobs.net

www.jntuworldupdates.org

Unit- IV Control Flow

Pascal’s final counter value is undefined (may have wrapped)

Iterators
Iterators are used to iterate over elements of containers such as sets and data
structures such as lists and trees
Iterator objects are also called enumerators or generators
C++ iterators are associated with a container object and used in loops similar to
pointers and pointer arithmetic:
Java supports generics similar to C++ templates
Iterators are similar to C++, but do not have the usual C++ overloaded iterator
operators:
TreeNode<Integer> T;

for (Integer i : T)
System.out.println(i);
Note that Java has the above special for-loop for iterators that is essentially
syntactic sugar for:
for (Iterator<Integer> it = T.iterator(); it.hasNext(); )
{ Integer i = it.next();
System.out.println(i);
}

Iterators typically need special loops to produce elements one by one, e.g. in Clu:
for i in int$from_to_by(first, last, step) do

end
While Java and C++ use iterator objects that hold the state of the iterator, Clu,
Python, Ruby, and C# use generators (=“true iterators”) which are functions that
run in “parallel” to the loop code to produce elements
The yield operation in Clu returns control to the loop body
The loop returns control to the generator’s last yield operation to allow it to
compute the value for the next iteration
The loop terminates when the generator function returns

6) Recursion
Recursion: subroutines that call themselves directly or indirectly (mutual
recursion)
Typically used to solve a problem that is defined in terms of simpler versions,
for example:
www.specworld.in 13 www.smartzworld.com

www.jntuworld.com Page 13

www.jntuworld.com
www.jntuworld.com www.jwjobs.net

www.jntuworldupdates.org

Unit- IV Control Flow

To compute the length of a list, remove the first element, calculate the length of
the remaining list in n, and return n+1
Termination condition: if the list is empty, return 0
Iteration and recursion are equally powerful in theoretical sense
Iteration can be expressed by recursion and vice versa
Recursion is more elegant to use to solve a problem that is naturally recursively
defined, such as a tree traversal algorithm
Recursion can be less efficient, but most compilers for functional languages are
often able to replace it with iterations

Tail-Recursive Functions
Tail-recursive functions are functions in which no operations follow the
recursive call(s) in the function, thus the function returns immediately after the
recursive call:
tail-recursive not tail-recursive
int trfun() int rfun()
{…{…
return trfun(); return rfun()+1;
}}
A tail-recursive call could reuse the subroutine's frame on the runtime stack,
since the current subroutine state is no longer needed
Simply eliminating the push (and pop) of the next frame will do
In addition, we can do more for tail-recursion optimization: the compiler
replaces tail-recursive calls by jumps to the beginning of the function
Tail-Recursion Optimization
Consider the GCD function:
int gcd(int a, int b)
{ if (a==b) return a;
else if (a>b) return gcd(a-b, b);
else return gcd(a, b-a);
}
a good compiler will optimize the function into:
int gcd(int a, int b)
{ start:
if (a==b) return a;
else if (a>b) { a = a-b; goto start; }
else { b = b-a; goto start; }
}
which is just as efficient as the iterative version:
www.specworld.in
int gcd(int a, int b) 14 www.smartzworld.com

www.jntuworld.com Page 14

www.jntuworld.com
www.jntuworld.com www.jwjobs.net

www.jntuworldupdates.org

Unit- IV Control Flow

{ while (a!=b)
if (a>b) a = a-b;
else b = b-a;
return a;
}

Converting Recursive Functions to Tail-Recursive Functions

Remove the work after the recursive call and include it in some other form as a
computation that is passed to the recursive call
For example, the non-tail-recursive function (define summation (lambda (f low
high)
(if (= low high)
(f low)
(+ (f low) (summation f (+ low 1) high)))))
can be rewritten into a tail-recursive function:
(define summation (lambda (f low high subtotal)
(if (=low high)
(+ subtotal (f low))
(summation f (+ low 1) high (+ subtotal (f low))))))

Applicative- and Normal-Order Evaluation

Evaluating before the call is known as applicative-order evaluation; Evaluating


only when the value is actually needed is known as normalorder evaluation.
Normal-order evaluation is what naturally occurs in macros. It also occurs in short-
circuit Boolean evaluation, call-by-name parameters and certain functional
languages

Lazy Evaluation
From the points of view of clarity and efficiency, applicative-order evaluation is
generally preferable to normal-order evaluation. It is therefore natural for it to be
employed in most languages. In some circumstances, however, normalorder
evaluation can actually lead to faster code, or to code that works when applicative-
order evaluation would lead to a run-time error. In both cases, what Scheme
provides for optional normal-order evaluation in the form of built-in functions
called delay and force.8 These functions provide an implementation of lazy
evaluation. In the absence of side effects, lazy evaluation has the same semantics
www.specworld.in
as normal order evaluation, but the 15implementation keeps trackwww.smartzworld.com
of which
www.jntuworld.com Page 15

www.jntuworld.com
www.jntuworld.com www.jwjobs.net

www.jntuworldupdates.org

Unit- IV Control Flow

expressions have already been evaluated, so it can reuse their values if they are
needed more than once in a given referencing environment. A delayed expression
is sometimes called a promise. The mechanism used to keep track of which
promises have already been evaluated is sometimes called memoization. Because
applicative-order evaluation is the default in Scheme, the programmer must use
special syntax not only to pass an unevaluated argument, but also to use it. In Algol
60, subroutine headers indicate which arguments are to be passed which way; the
point of call and the uses of parameters within subroutines look the same in either
case.
A common use of lazy evaluation is to create so-called infinite or lazy data
structures that are “fleshed out” on demand. The following example, adapted

fromthe Schememanual [ADH+98, p. 28], creates a “list” of all the natural


numbers.
(define naturals
(letrec ((next (lambda (n) (cons n (delay (next (+ n 1)))))))
(next 1)))
(define head car)
(define tail (lambda (stream) (force (cdr stream))))

Here cons can be thought of, roughly, as a concatenation operator. Car returns
the head of a list; cdr returns everything but the head. Given these definitions,
we can access as many natural numbers as we want:
(head naturals) _⇒1
(head (tail naturals)) _⇒2
(head (tail (tail naturals))) _⇒3
The list will occupy only as much space as we have actually explored. More
elaborate lazy data structures (e.g., trees) can be valuable in combinatorial search
problems, in which a clever algorithm may explore only the “interesting” parts of
a potentially enormous search space.

www.specworld.in 16 www.smartzworld.com

www.jntuworld.com Page 16

www.jntuworld.com

You might also like