You are on page 1of 59

Control Abstraction

Topics

• Abstraction of control
• Methods of parameter passing
• Higher-order functions
◦ Functions as parameters
◦ Functions as results
• Exception handling
Abstraction

• Identify important properties of the thing that we want to describe


• Concentrate on relevant questions and ignore the others
• What is relevant depends on the scope of the project
Abstraction of control

• Subprograms, blocks, parameters

double P (int x) {
double z;
...
return expr;
}

• Without knowing the context


◦ Specify P
◦ Write P
◦ Use P
Parameters

• Terminology
◦ Declaration/definition
int f (int n) {return n+1;}
◦ Use
x=f(y+3);
◦ n and y+3 are the formal parameter and the actual parameter
Communication between the function and the caller

• Value returned

int f() {return 1;}

• Parameters pass values from/to main and proc


Methods of parameter passing

• Two principal methods


◦ By value
• The value is the actual one assigned to the formal paramater,
that is treated like a local variable
• Transmission from main to proc
• Modifications to the formal parameter do not affect the actual
one
◦ By reference (or variable)
• A reference (address) to the actual parameter. References to
the formal parameter are references to the actual one (aliasing)
• Transmission from and to main and proc
• Modifications to the formal parameter are transferred to the
actual one
Call by value

• At the end y has the value 1

void foo (int x) { x = x+1; }

y = 1;
foo(y+1);

• The formal parameter x is a local variable (on the stack)


• At the call, y+1 is evaluated, and its value is assigned to the formal
parameter x
• There is no link between x in the body of foo and y
• On exit from foo, x is destroyed (removed from the stack)
• It is not possible to transmit data from foo via the parameter
• Expensive for large amounts of data, as they must be copied
• Used in Java, Scheme, Pascal (default), and C
Call by name (reference)

• In the following, y is equal to 2 at the end

void foo (reference int x){ x = x+1;}


...
y = 1;
foo(y);

• A reference (address, pointer) is passed


• x is an alias of y
• The actual value is an l-value
• On exit from foo, the link between x and the address of y is destroyed
• Transmission: Two-way between foo and the caller
• Efficient call, but indirection in body
• Pascal (var), C using pointers
Read-only

• Call-by-value could be expensive


◦ Large data items are copied even if they are not modifed
◦ Read-only call (Modula-3, ANSI C const)
• Procedures are not allowed to change the value of the formal
parameter (could be statically controlled by the compiler)
• Could be at the discretion of the compiler (“large” parameters
passed by reference, “small” by value)
◦ In Java: final
void foo (final int x){
means that x cannot be modified
Call by result

• Here, y is 8

void foo (result int x) {x = 8;}


...
y = 1;
foo(y);

• The variable x is a local variable (on the stack)


• When foo ends, the value of x is assigned to the current y
• No link between x in the body of foo and y
• When foo returns, x is destroyed (removed from the stack)
• Information cannot be sent from the caller of foo via the parameter
• Large copying cost for large data
• Ada: out
Call by value/result

• Here, y is 8

void foo (value-result int x) {x = x+1;}


...
y = 8;
foo(y);

• The variable x is like a local variable (on the stack)


• When foo is called, the current value is assigned to x
• When foo ends, the value of the formal parameter is assigned to the
actual one
• No link between x in the body of foo and y
• When foo returns, x is destroyed (removed from the stack)
• Large copying cost for large data
• Ada: in out, but only for small data (large data call by reference)
Call by value vs. reference

• Call by value
◦ Simple semantics. The body of the procedure does not need to
know how the procedure was called (referential transparency)
◦ Implementation fairly simple
◦ Call could be expensive
◦ Need for other mechanisms to communicate with the called pro-
cedure
• Call by reference
◦ Complicated semantics: Aliasing
◦ Simple implementation
◦ Call is efficient; Reference to formal parameter slightly more ex-
pensive
Call by name

• Copy rule
◦ A call to P is the same as executing the body of P after substi-
tuting the actual parameters for the formal one
• “Macro expansion”, implemented in a semantically correct way
• Appears to be simple
Call by name

• Default in Algol-W and other languages from that time

int y;
void foo (name int x) {x= x + 1; }
...
y = 1;
foo(y);

• This increments y
Call by name

• Which environment is relevant for x after substituting y?

int y;
void fie (name int x){
int y;
x = x + 1; y = 0;
}
...
y = 1;
fie(y);

• Static scoping: The called procedure


• fie increments the actual y via the formula, then creates, and de-
stroys, a new one
Call by name

• We have to pass a pair <exp,env> , where


◦ exp is the actual parameter, not evaluated
◦ env is the the evaluation environment (static scoping)
• Every time the formula is used, exp is evaluated in env

int y ;
void fie (int x ){
int y;
x = x + 1; y = 0;
}
...
y = 1;
fie(y);

• More expensive, as the whole environment must be passed


• Only used in Algol-60 and Algol-W
Call by name: Implementation

• How do we pass the pair <exp,env>?


◦ A pointer to the text of exp
◦ A pointer to the static chain (on the stack) of the activation record
of the calling block
• This also lets us pass functions as arguments to other procedures
Higher-order functions

• Some languages allow


◦ Passing functions as arguments to procedures
◦ Returning functions as results of procedures
• In both cases: How do we manage the environment?
• Simplest case
◦ Functions as arguments
◦ Use a pointer to the activation record in the stack
• More complicated
◦ Function returned as the result of a procedure
◦ We must maintain an activation record of the resulting function,
but not on the stack
Function as parameter of a procedure

• Call by name is a special case


◦ Use a function without arguments
• Example (based on Pascal)
◦ Note: In C, one can have a function as an argument, but no nested
functions (so calling based on a pointer suffices)
Function as parameter of a procedure

• Example:

int x=4; int z=0;


int f (int y){
return x*y;}
void g ( int h(int n) ) {
int x;
x = 7;
z = h(3) + x ;
}
{int x = 5;
g(f); }

• Three declarations of x
• When f is called (via h), which x is used?
◦ Static scoping: The external x
◦ Dynamic scoping: Both would make sense
Binding rules

• When a procedure is passed as a parameter, this creates a reference


between a name (formal, h) and a procedure (actual f)
• Which non-local environment applies when f is executed, called via h?
◦ Environment at the moment of creation of the link (deep binding):
always used with static scoping
◦ Environment at the moment of the call (shallow binding): Can be
used with dynamic scoping
Deep and shallow binding: Example

{int x=1;
int f(int y){
return x+y;
}
void g (int h (int b)){
int x=2;
return h(3)+x;
}
...
{int x=4;
int z=g(f);
}
}
Deep and shallow binding: Example

• h(3) calls f : Which x is accessed?


• Static scope:
◦ Deep: x=1 (external)
◦ Shallow: x=1 (external)
◦ Scope rules are sufficient
• Dynamic scope:
◦ Deep: x=4 (calling block)
◦ Shallow: x=2 (internal block)
Deep binding and closure
Deep binding and closure

{int x=1;
int f(int y){
return x+y;
}
void g (int h(bool b)){
int x=2;
return h(3)+x;
}
...
{int x=4;
int z = g(f);
}
}
Closure

• Both the link to the code of the function, as well as its nonlocal
environment are passed to the function
• The procedure passed as parameter
◦ Allocates the activation record
◦ Takes the pointer to the static chain of the closure
Summary: Functions as parameters

• Closure to maintain pointers in the static environment of the body of


a function
• When called, the pointer to the static chain is determined via the
closure
• All the pointers of the static chain always point to the stack
◦ Activation records can be “jumped over” to access nonlocal vari-
ables
◦ Deallocation of activation records using stack LIFO
Dynamic scoping: Implementation

• Shallow binding
◦ No need to do anything special
• To access x use the stack
• Use the standard structures (A-list, CRT)
• Deep binding
◦ Use some form of closure to “freeze” the scope so that it can be
reactivated later
Another example

• threshhold
◦ Selection condition for database
◦ Function older_than_threshhold: deep binding seems appro-
priate
• line_length
◦ print_person: Shallow binding seems appropriate
Another example
Deep vs. shallow binding

• Summary
◦ Dynamic scope
• Possible with deep binding
◦ Implementation with closure
• Or shallow binding
◦ No special implementation needed
◦ Static scope
• Always uses deep binding
◦ Implemented with closure
• At first glance no difference between deep and shallow binding
◦ The static scoping rules determine which nonlocal value to
use
• That is not the case: There may be dynamically several in-
stances of a block that declare a non-local name (for example
with recursion)
Returning a function: Simple case

{ int x=1
void->int F() {
int g() {
return x+1;
}
return g;
}
void->int gg = F();
int z =gg ();
}
Returning a function: Simple case

• F returns a closure
• Modify the abstract machine to manage a “call by closure” as in gg();
Returning a function: Complex case

void->int F() {
int x=1;
int g() {
return x+1;
}
return g;
}
void->int gg=F();
int z=gg();
Fig. 7.18 Functions as result void->int F () {
and stack discipline int x = 1;
int g () {
return x+1;
}
return g;
Returning a function: Complex case
}
void->int gg = F();
int z = gg();

Fig. 7.19 Activation records


for Fig. 7.18

when a function is called via a formal parameter, when a function whose value is
obtained dynamically (like gg), the static chain pointer of its activation record is
determined using the associated closure (and not the canonical rules discussed in
• F returns a closure
Sect. 5 .5 .1, which would be of no help).
The general situation, moreover, is much more complex. If it is possible to re-
• But where is the association for x after F terminates?
turn a function from the inside of a nested block, it is possible to arrange that its
evaluation environment will refer to a name that, according to stack discipline, is
going to be destroyed. Let us consider, indeed, the code in Fig. 7.18, where we
have moved the declaration of x to the inside of F. Figure 7.19 shows the activation
record arrangement when gg() is called.
When the result of F() is assigned to gg, the closure which forms its value
points to an environment containing the name x. But this environment is local to F
and will therefore be destroyed on its termination. How is it possible, then, to call
gg later than this without producing a dangling reference to x? The reply can only
be drastic: abandon stack discipline for activation records, so that they can then stay
Conclusions

• Use closure
• But the activation record remains forever
◦ Loss of stack FIFO property
• How do we then implement a “stack”?
◦ No automatic deallocation
◦ Activation record on heap
◦ Static or dynamic chain connects the record
◦ Call garbage collector when needed
Exceptions: “Structured exit”

• Conclude part of a computation


◦ Exit from a construct
◦ Pass data via a jump
◦ Return control to the most recent activation point
◦ Activation records that are no longer needed are deallocated
• Other resources, including space on the heap, can be freed
◦ Two constructs
• Declaration of the exception manager:
◦ Location and code
• Commands for raising exceptions
Example

class EmptyExcp extends Throwable {int x=0;};

int average (int[] v) throws EmptyExcp(){


if (length(V)==0) throw new EmptyExcp();
else {int s=0; for (int i=0, i<length(V), i++) s=s+V[i];}
return s/length(V);
};
...
try{ ...
average (W);
...

}
catch (EmptyExcp e){write(’Empty array’);}
Example

• average calculates the average of a vector


• If the vector is empty, it raises an exception
• The exception handler is in a static block of protected code
Exception propagation

• If the exception is not handled by the current procedure


◦ The procedure terminates, and the exception is raised again at the
calling point
◦ If the caller does not handle the exception, the caller raises the
exception, etc.
◦ We either find an exception handler, or reach the top level, that
provides a default handler
• The corresponding frames are removed from the stack, restoring the
values of the registers as we do so
• Each routine has a “hidden” exception handler, that restores the state
an propagates the exceptions along the stack
Exceptions are propagated along a dynamic chain

• Which of the exception handlers are called?


• Note: When the argument of g is the result of the execution, the
handler is not determined statically
Exceptions are propagated along the dynamic chain
Implementation of exceptions

• Simple:
◦ At the beginning of a protected block: Insert on the stack of the
caller
◦ When an exception is raised
• Remove the caller from the stack and check if this is the right
one
• If not, raise a new exception and repeat
◦ Inefficient in the most frequent case where we do not find the
handler
• Each attempt must insert and remove objects from the stack
• Better solution: Table of addresses
Exercise 1
Say what will be printed by the following code fragment written in a pseudo-
language which uses static scope; the parameters are passed by value.

{int x = 2;
int fie(int y){
x = x + y; }
{int x = 5;
fie(x);
write(x);
}
write(x);
}
Exercise 2
Say what is printed by the code in the previous exercise if it uses dynamic
scope and call by reference.
Exercise 3
State what is printed by the following fragment of code written in a pseudo-
language which uses static scope and passes parameters by reference.

{int x = 2;
void fie(reference int y){
x = x + y;
y = y + 1; }
{int x = 5;
int y = 5;
fie(x);
write(x);
}
write(x);
}
Exercise 4
State what will be printed by the following code fragment written in a
pseudo-language which uses static scope and passes its parameters by value
(a command of the form foo(w++) passes the current value of w to foo
and then increments it by one).

{int x = 2;
void fie(value int y){
x = x + y; }
{int x = 5;
fie(x++);
write(x);
}
write(x);
}
Exercise 5
State what will be printed by the following fragment of code written in a
pseudo-language which uses static scope and call by name.

{int x = 2;
void fie(name int y){
x = x + y; }
{int x = 5;
{int x = 7
}
fie(x++);
write(x);
}
write(x);
}
Exercise 6
State what will be printed by the following code written in a pseudo-language
which uses dynamic scope and call by reference.

{int x = 1;
int y = 1;
void fie(reference int z){
z =x+y+z;
}
{int y = 3;
{int x = 3 }
fie(y);
write(y);
}
write(y);
}
Exercise 7
State what will be printed by the following fragment of code written in a
pseudo-language which uses static scope and call by reference.

{int x = 0;
int A(reference int y) {
int x =2;
y=y+1;
return B(y)+x;
}
int B(reference int y){
int C(reference int y){
int x = 3;
return A(y)+x+y;
}
if (y==1) return C(x)+y;
else return x+y;
}
write (A(x));
}
Exercise 8
State what will be printed by the following code fragment written in a
pseudo-language permitting reference parameters (assume Y and J are passed
by reference).

int X[10];
int i = 1;
X[0] = 0;
X[1] = 0;
X[2] = 0;
void foo (reference int Y,J){
X[J] = J+1;
write(Y);
J++;
X[J]=J;
write(Y);
}
foo(X[i],i);
write(X[i]);
Exercise 9
State what will be printed by the following code fragment written in a
pseudo-language which allows value-result parameters:

int X = 2;
void foo (valueresult int Y){
Y++;
write(X);
Y++;
}
foo(X);
write(X);
Exercise 10
The following code fragment, is to be handed to an interpreter for a pseudo-
language which permits constant parameters:

int X = 2;
void foo (constant int Y){
write(Y);
Y=Y+1;
}
foo(X);
write(X);

What is the most probable behaviour of the interpreter?


Exercise 11
Say what will be printed by the following code fragment which is written in
a pseudo-language allowing name parameters:

int X = 2;
void foo (name int Y){
X++;
write(Y);
X++;
}
foo(X+1);
write(X);
Exercise 12
Based on the discussion of the implementation of deep binding using clo-
sures, describe in detail the case of a language with dynamic scope
Exercise 13
Consider the following fragment in a language with exceptions and call by
value-result and call by reference:

{int y=0;
void f(int x){
x = x+1; throw E; x = x+1;
}
try{ f(y); } catch E {}; write(y);
}

State what is printed by the program when parameters are passed: (i) by
value-result; (ii) by reference
Exercise 14
In a pseudo-language with exceptions, consider the following block of code:

void ecc() throws X { throw new X();


}
void g (int para) throws X {
if (para == 0) {ecc();}
try {ecc();} catch (X) {write(3);}
}
void main () {
try {g(1);} catch (X) {write(1);}
try {g(0);} catch (X) {write(0);}
}

Say what is printed when main() is executed.


Exercise 15
The following is defined in a pseudo-language with exceptions:

int f(int x){


if (x==0) return 1; else if (x==1) throw E;
else if (x==2) return f(1);
else try {return f(x-1);} catch E {return x+1;}
}

What is the value that is returned by f (4)?

You might also like