0% found this document useful (0 votes)
9 views11 pages

Apppl

Uploaded by

amarakim3105
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views11 pages

Apppl

Uploaded by

amarakim3105
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 11

Principles of

Programming
Languages
Assignment No-1

Topics:
1. Modules
2. Module Types and Classes
3. Binding of Referencing Environments

Submitted by,
Aparna.J.S
S7 IT
Roll No:06
MODULES
A major challenge in the construction of any large body of software is how to
divide the
effort among programmers in such a way that work can proceed on multiple
fronts
simultaneously. This modularization of effort depends critically on the notion
of information hiding, which makes objects and algorithms invisible, whenever
possible, to portions of the system that do not need them. Properly
modularized code reduces the “cognitive load”on the programmer by
minimizing the amount of information required to understand any given
portion of the system. In a well-designed program the interfaces between
modules are as “narrow” (i.e., simple) as possible, and any design decision
that is likely to change ishidden inside a single module. This latter point is
crucial, since maintenance (bug fixes and enhancement) consumes much more
programmer time than does initial construction for most commercial
software.In addition to reducing cognitive load, information hiding reduces
the risk of name conflicts: with fewer visible names, there is less chance that a
newly introduced name will be the same as one already in use. It also
safeguards the integrity of data abstractions: any attempt to access objects
outside of the subroutine(s) to which they belong will cause the compiler to
issue an “undefined symbol” error message. Finally, it helps to
compartmentalize run-time errors: if a variable takeson an unexpected value,
we can generally be sure that the code that modified it is in the variable’s
scope.

Encapsulating Data and Subroutine


The information hiding provided by nested subroutines is limited to objects
whose lifetime is the same as that of the subroutine in which they are
hidden. When control returns from a subroutine, its local variables will no
longer be live: their values will be discarded. We have seen a partial solution
to this problem in the form of thesave statement in Fortran and the static and
own variables of C and Algol.

Static variables allow a subroutine to have “memory”—to retain information


from one invocation to the next—while protecting that memory from
accidental access or modification by other parts of the program. Put another
way, static variables allow programmers to build single-subroutine
abstractions. Unfortunately, they do not allow the construction of abstractions
whose interface needs to consist of more than one subroutine.

Modules as Abstractions
A module allows a collection of objects—subroutines, variables, types, and
so
on—to be encapsulated in such a way that objects inside are visible to each
other,
but objects on the inside are not visible on the outside unless explicitly
exported, and
(in many languages) objects outside are not visible on the inside unless
explicitly
imported. Note that these rules affect only the visibility of objects; they do not
affect
their lifetime.

As an example of the use of modules, consider the stack abstraction Bindings


to variables declared in a module are inactive outside the module, not
destroyed. In our stack
top have the same lifetime they would have had if not enclosed in the
module. If
stack is declared at the program’s outermost nesting level, then and top retain
theirvalues throughout the execution of the program, though they are visible
only to the code inside push and pop.If stack is declared inside some
subroutine sub , then and top have the same lifetime as the local variables of
sub.If stack is declared inside some other module mod, then they would have
had if not enclosed in either module. Type stack_index, which is also declared
inside stack, is likewise visible only inside push and pop.The issue of lifetime
is not relevant for types or constants, since they have no mutable state.

CONST stack_size = ...


TYPE element = ...
...
MODULE stack;
IMPORT element, stack_size;
EXPORT push, pop;
TYPE
stack_index = [1..stack_size];
VAR
s : ARRAY stack_index OF element;
top : stack_index; (* first unused slot *)
PROCEDURE error; ...
PROCEDURE push(elem : element);
BEGIN
IF top = stack_size THEN
error;
ELSE
s[top] := elem;
top := top + 1;
END;
END push;
PROCEDURE pop() : element; (* A Modula-2 function is just a *)
BEGIN (* procedure with a return type. *)
IF top = 1 THEN
error;
ELSE
top := top - 1;
RETURN s[top];
END;
END pop;
BEGIN
top := 1;
END stack;
VAR x, y : element;
...
push(x);
...
y := pop;

Modules as Managers
Modules facilitate the construction of abstractions by allowing data to be
made private to the subroutinesthat use them.The manager idiom requires
additional subroutines to create/initialize and possibly destroy stack instances,
and it requires that every subroutine (push, pop,create) take an extra
parameter, tospecify the stack in question. Clu adopts the position that every
module (“cluster”)is the manager for a type. Data declared in the cluster
(other than static variables insubroutines) are automatically the
representation of the managed type, and there are special language features
to export an opaque version of the representation to users of the type.

CONST stack_size = ...


TYPE element = ...
...
MODULE stack_manager;
IMPORT element, stack_size;
EXPORT stack, init_stack, push, pop;
TYPE
stack_index = [1..stack_size];
stack = RECORD
s : ARRAY stack_index OF element;
top : stack_index; (* first unused slot *)
END;
PROCEDURE init_stack(VAR stk : stack);
BEGIN
stk.top := 1;
END init_stack;
PROCEDURE push(VAR stk : stack; elem : element);
BEGIN
IF stk.top = stack_size THEN
error;
ELSE
stk.s[stk.top] := elem;
stk.top := stk.top + 1;
END;
END push;
PROCEDURE pop(VAR stk : stack) : element;
BEGIN
IF stk.top = 1 THEN
error;
ELSE
stk.top := stk.top - 1;
RETURN stk.s[stk.top];
END;
END pop;
END stack_manager;

MODULE TYPES AND CLASSES


An alternative solution to the multiple instance problem can be found in
Simula,Euclid, and (in a slightly different sense) ML, which treat modules as
types,rather than simple encapsulation constructs. Given a module type, the
programmer can declare an arbitrary number of similar module objects.

const stack_size := ...


type element : ...
...
type stack = module
imports (element, stack_size)
exports (push, pop)
type
stack_index = 1..stack_size
var
s : array stack_index of element
top : stack_index
procedure push(elem : element) = ...
function pop returns element = ...
...
initially
top := 1
end stack

Object Orientation
As an extension of the module-as-type approach to data abstraction, many
languages now provide a class construct for object-oriented
programming.To first approximation, classes can be thought of as module
types that have been augmented with an inheritance mechanism.
Inheritance allows new classes to be defined as extensions or refinements
of existing classes. Inheritance facilitates a programming style in which all or
most operations are thought of as belonging to objects, and in which new
objects can inherit most of their operations from existing objects, without
the need to rewrite code. Classes have their roots in Simula-67. They are
the central innovation of object-oriented languages such as Smalltalk, Eiffel,
C++, Java, and C#. They are also fundamental to several scripting languages,
notably Python and Ruby.

Module types and classes (ignoring issues related to inheritance) require only
simple changes to the scope rules defined for modules in the previous
subsection.Every instance A of a module type or class (e.g., every stack) has a
separate copy of the module or class’s variables. These variables are then
visible when executing one of A’s operations. They may also be indirectly
visible to the operations of some other instance B if A is passed as a
parameter to one of those operations. This rule makes it possible in most
object-oriented languages to construct binary (or moreary) operations that
can manipulate the variables of more than one instance of a class. In C++, for
example, we could create an operation that determines which of two stacks
contains a larger number of elements:

class stack {
...
bool deeper_than(stack other) { // function declaration
return (top > other.top);
}
...
}
...
if (A.deeper_than(B)) ...

Within the deeper_than operation of stack A, top refers to A.top. Because


deeper_than is an operation of class stack,however,it is able to refer not
only to the variables of A(which it can access directly by name), but also to
thevariables of any other stack to which it has a reference. Because these
variables belong to a different stack,deeper_than must name that stack
explicitly, as for example in other.top. In a module-as-manager style program,
of course, module subroutines would access all instance variables via
parameters.

Modules Containing Classes


While there is a clear progression from modules to module types to classes, it
is not necessarily the case that classes are an adequate replacement for
modules in all cases. Suppose we are developing a complex “first person”
game. Class hierarchies may be just what we need to represent characters,
possessions, buildings, goals,and a host of other data abstractions. At the
same time, especially on a project with a large team of programmers, we will
probably want to divide the functionality of the game into large-scale
subsystems such as graphics and rendering, physics, and strategy. These
subsystems are really not data abstractions, and we probably don’t want the
option to create multiple instances of them. They are naturally captured with
traditional modules. Many applications have a similar need for both multi-
instance abstractions and functional subdivision. In recognition of this fact,
many languages, including C++,Java, C#, Python, and Ruby, provide separate
class and module mechanisms.

BINDING OF REFERENCE
ENVIRONMENTS
Static scope rules specify that the referencing environment depends on the
lexical nesting of program blocks in which names are declared. Dynamic scope
rules specify that the referencing environment depends on the order in which
declarations are encountered at run time.

Program to illustrate the imporatance of binding rules:

type person = record


...
age : integer
...
threshold : integer
people : database
function older
than
threshold(p : person) : boolean
return p.age

threshold
procedure print
person(p : person)
– – Call appropriate I/O routines to print record on standard output.
– – Make use of nonlocal variable line
length to format data in columns.
...
procedure print
selected
records(db : database;
predicate, print
routine : procedure)
line
length : integer
if device
type(stdout) = terminal
line
length := 80
else
– – Standard output is a file or printer.
line
length := 132
foreach record r in db
– – Iterating over these may actually be
– – a lot more complicated than a ‘for’ loop.
if predicate(r)
print
routine(r)
– – main program
...
threshold := 35
print
selected
records(people, older
than
threshold, print
person)

Subroutine Closure
Deep binding is implemented by creating an explicit representation of a
referencing environment (generally the one in which the subroutine would
execute if called at the present time) and bundling it together with a reference
to the subroutine. The bundle as a whole is referred to as a closure. Usually
the subroutine itself can be represented in the closure by a pointer to its code.
In a language with dynamic scoping, the representation of the referencing
environment depends on whether the language implementation uses an
association list or a central reference table for run-time lookup of names.

Deep binding is generally the default in languages with static (lexical)


scoping.At first glance, one might be tempted to think that the binding time of
referencing environments would not matter in languages with static scoping.
After all, the meaning of a statically scoped name depends on its lexical
nesting, not on the flow of execution, and this nesting is the same whether it is
captured at the time a subroutine is passed as a parameter or at the time the
subroutine is called. The catch is that a running program may have more than
one instance of an object that is declared within a recursive subroutine. A
closure in a language with static scoping captures the current instance of
every object, at the time the closure is created. When the closure’s subroutine
is called, it will find these captured instances, even if newer instances have
subsequently been created by recursive calls.

First-ClassValues and Unlimited Extent


In general, a value in a programming language is said to have first-class
status if it can be passed as a parameter, returned from a subroutine, or
assigned into a variable. Simple types such as integers and characters are
first-class values in most programming languages. By contrast, a “second-
class” value can be passed as a parameter, but not returned from a subroutine
or assigned into a variable, and a “third-class” value cannot even be passed as
a parameter.

Deep binding in pascal:

program binding_example(input, output);


procedure A(I : integer; procedure P);
procedure B;
begin
writeln(I);
end;
begin (* A *)
ifI>1
then
P
else
A(2, B);
end;
procedure C; begin end;
begin (* main *)
A(1, C);
end
Object Closures

The referencing environment in a closure will be non trivial only when passing
a nested subroutine. This means that the implementation of first-class
subroutines is trivial in a language without nested subroutines. At the same
time, it means that a programmer working in such a language is missing
a useful feature: the ability to pass a subroutine with context. In object-
oriented languages, there is an alternative way to achieve a similar effect: we
can encapsulate our subroutine as a method of a simple object, and let the
object’s fields hold context for the method.

An object closure in java:

interface IntFunc {
public int call(int i);
}
class PlusX implements IntFunc {
final int x;
PlusX(int n
){x=n;}
public int call(int i) { retur
ni+x;}
}
...
IntFunc f = new PlusX(2);
System.out.println(f.call(3)); // prints 5

Function objects in cpp:

class int_func {
public:
virtual int operator()(int i) = 0;
};
class plus_x : public int_func {
const int x;
public:
plus_x(int n) : x(n) { }
virtual int operator()(int i) { retur
ni+x;}
};
...
plus_x f(2);
cout << f(3) << "\n";
// prints 5
Delegates in C#:

static int Plus2(int i) { retur


ni+2;}
...
IntFunc f = new IntFunc(Plus2);
Console.WriteLine(f(3)); // prints 5
class PlusX {
int x;
public PlusX(int n
){x=n;}
public int call(int i) { retur
ni+x;}
}
...
IntFunc g = new IntFunc(new PlusX(2).call);
Console.WriteLine(g(3));
// prints 5

Delegates and unlimited extents:

static IntFunc PlusY(int y) {


return delegate(int i) { retur
ni+y;};
}
...
IntFunc h = PlusY(2);
REFERENCES

Programming Language Pragmatics, 3rd edition, Michael L.Scott

You might also like