[CSUSB] >> [CompSci] >> /pool/faculty/dick/cs320/sebesta/09

CS320 Notes on Subprograms

http://www.csci.csusb.edu/dick/cs320/sebesta/09.html
http://www.csci.csusb.edu/dick/cs320/sebesta/09

Contents

	9.1 Introduction
	9.2 Fundamentals
	9.3 Design Issues
	9.4 Local Referencing Environments
	9.5 Parameter Parsing: theory and practice
	9.6 Subprograms as Parameters
	9.7 Overloading
	9.8 Generic Subprograms
	9.9 Separate vs Independent
	9.10 Functions
	9.11 Nonlocal Environments
	9.12 User defined overloaded operators
	9.13 Coroutines

    Exercises
    Problems

9.1 Introduction.

	Humans can design complicated things that work because we use
	a process of 
		abstraction
	This means abstracting the important features,
	working with these, 
	and then
	take each feature and work out the details.

	Abstraction means replacing a lot of detail by a more
	or less imaginary single item.

	Pretending the Many is actually One.

        One kind of abstraction is
		procedural abstraction
	One command (call) mapped into many commands (body).


	Two ways of getting desired effect:
                    (1) Open subroutine or Macro
                    (2) Closed subroutine, procedure,...
				Procedure: no value returned/void
				Function:  Returns a value.
                
	    Open subroutine or Macro
		 During or before compilation replaces subprogram call 
		   by its expansion.

	    Closed subroutine, procedure, function,...                    
		 One copy is compiled. Each subprogram call transfers
		    control to start of code and this code returns to
		    the instruction after call.

9.2 Fundamentals - Learn the definitions.

	Distinguish:-
	    CALL: A call of the subprogram
	    NAME: The name of the subprogram/function
	    DEFINITION: The Definition  of the subprogram
		Says precisely how it does it, in detail
		as well as how to use it.
	    DESCRIPTION: A description of how to call it.
		Says how to use it.
			Parameter types
			Procedure or function
			If function: what is returned
				(C/C++/Java Prototype)

	parameters = arguments
		data that is given by the caller to the body.

	Procedures are NOT functions!
		A procedure in C is a function that returns no useful value.  

	The book uses the word 'protocol' for a description of
	how a subprogram is to be used:
			Parameter types
			Procedure or function
			If function: what is returned
	In C/C++ this is called a 'prototype'
		(Easily confused with a prototype version of a program.)
	Theorists call it a 'signature'.

9.3 Issues - learn

	This sets the agenda for the rest of the chapter.

9.4 Local Environment

The C code on page 332 has the effect of accumulating all the successive
arrays that it adds up.  What do you change so that it doesn't do this but
gives the sum of each array alone. (easy in C and C++)

How do you modify it so that: adder(array, length) accumulates more and
clear() resets the running sum to zero.(tricky in C, easy in C++)

How do you further make it impossible for a user to accidentally destroy
the current value of sum by assigning a value  other than zero
to it? (Tough in C, Easy in C++)

9.5 Parameter passing

	Why parameters?
	    BASIC and COBOL do without.
	Semantics:  Learn!
	    in
	    out
	    inout
	Implementation models: Learn!
	    value
	    result
	    value-result
	    reference
	    Name  -> now reborn as late binding.
	Examples
		FORTRAN
		ALGOL 60, 68
		Pascal
		Ada
		C
		Note a dirty secret about C macros.
		  They pass parameters by name!

	Try the following macro in a program based on pages 315..316.
	#define SIGMA(s,a,i,n) for(i=1,s=0.0;i<=n;s+=a,i++)

	Here is an example of the code:
	http://www.csci.csusb.edu/dick/cs320/sebesta/09.name.cc
	main()
	{
		int i;
		float sum;

		SIGMA(sum, i, i, 10);
		cout << sum <<endl;

		SIGMA(sum, i*i, i, 10);
		cout << sum <<endl;

		int j;
		double dimsum;
		SIGMA(dimsum, j*j*j, j, 10);
		cout << dimsum <<endl;

		SIGMA(dimsum, (10-i)/(double)i, i, 10);
		cout << dimsum <<endl;

	}

	Find a way to use SIGMA so that it does not compile!

Type-checking

		Which is more expensive: 1000 programmers 
		checking the types of their arguments themselves, or 
		writing type checking into a language?

Implementation Details.

		The Stack.
		Thunks
		Arrays?

Examples

Is it possible to write a perfect SWAP(x,y) macro in C that works for all
possible calls?  Here is the normal code:
#define	SWAP(x,y)	{ double t=x; x=y; y=t; }
Find 3 or 4 ways to make this either not compile, or else compile
and do the wrong thing.  

How does an inline void static function in C++ differ from a
macro in C?   (list 3 answers)


9.6 Subprograms as parameters

Can a function name be used in C without calling it?  If so what
does it mean and what can you do with it.

Notice the complications that arise with determining the referencing
environment:  start from which call/definition?  Pages 351-352.
A language designers headache.


9.7 Overloading

	One identifier + many possible types
	defines a special purpose function.

9.8 Generics

	Year	Has		Hasn't
	1983	Ada		All languages
	1995	Ada		All languages
	1996	Ada,C++ 	The rest
	20??    Ada,C++,Java	?

9.9 Separate Compilation


Look in the source code for our languages
to see how separate compilation is organized in C:
	http://www.csci.csusb.edu/dick/cs320/lisp/src/
	http://www.csci.csusb.edu/dick/cs320/prolog/src/

9.10 Design for functions

	Side effects???
	Type of result


9.11 Access non-local Environments

	COMMON
	Nested
	Export/import

	Again C is a little more sophisticated than the book
	suggests.  A declaration that is external to all
	functions, and is described as static is shared
	only by the functions that are in the same file.

	C++ Classes and Ada Packages provide a useful compromise:
		Set of subprograms share a set of common variables
		That can be made private.

9.12 DIY Operators


User defined overloaded operators

9.13 Coroutines

I find that a variation of the co-routine is the Semi-coroutine 
or restartable subprogram.  They aren't in the book but are
very helpful for making the structures of solutions of some
problems fit the structure of the problem.  They are not in the book
because they have never been implemented in a high-level programming
language.  However they shine a light on how normal co-routines
work and are easier to use and implement than co-routines.

A semicoroutine is a subprogram that hides within it the
'resume' mechanism so that whenever it is called it
restarts at the point that it last returned. A semi-coroutine
can return a value each time it suspends operation and then can
be called a cofunction.

It is a simple kind of pseudo-parallelism.  It is
also easy to simulate using low-level ideas like gotos
and labels.

Semi-co-routines are asymmetrical.  They are called like normal
subprograms (as functions or procedures).  Internally they
restart themselves.  They hide this from the caller.
You call restartable functions/procedures/subprograms in the normal way.  
Invisibly
and automatically they remember their previous state on exit,
and when called, restart
themselves just after the last return.

Semicoroutines are efficient, easy to write, and easy to use... it is a 
pity that they have never been incorporated into any languages.  On the 
other hand they are easy to code in COBOL, FORTRAN, ASSEMBLER, C or C++.


Suppose we had a version of C/C++ with semi-co-routines we can write functions
that are designed to handle whole streams of data correctly -- ones that
mirror the structure of the data being handled.  A very good example of
these are function that recognize and parse streams of data according to
various grammars.  Here is an example.

Suppose we have an incoming stream of characters  and want to classify its
characters according to this XBNF:
	input_stream::= #( (odd_a | non_a )  (even_a | non_a ) ).
(Given input "aaa" this classifies the characters like this:
	a: odd a
	a: even a
	a: odd a
and in string "aba":
	a: odd a
	b: non_a
	a: odd a
)

We could (If C++ had two extra statements) write:

string example(char c){
	restart();
	while(true){
	  if( c == 'a' )
	  	resume("odd a");
	  else resume("not an a");

	  if( c == 'a' )
		resume( "even a" );
	  else resume("not an a");
	}
}
Each time the above resume(...)s it stores information before returning 
control out of example(), that allows the restart() function to jump back 
to the next statement after the last resume statement.


A compiler writer would make the restart() and resume() functions work 
directly handling the stack plus some hidden static storage.  Application 
programmers can instead write a C or C++ that uses static storage, a switch
statement, goto's and labels that simulates the above effect.  Similar code
is used in other languages like COBOL and Pascal.

However we can also do this ourselves by adding static storage for the
state -- the number of the last resume + code to transfer control back
to the correct place.  A convention is to use Q0,Q1,Q2, for the labels
and QS for the variable because theorists use Q for the state of a automaton
or machine.

string example(char c){
    /*restart*/
	static int QS=0;

switch(QS){case 0:goto Q0; case 1:goto Q1; case 2:goto Q2; case 3:goto Q3;
case 4:goto Q4; }

	Q0:
	while(true){
	  if( c == 'a' )
	  	{QS=1; return("odd a"); Q1:; }
	  else {QS=2; return("not an a"); Q2:; }

	  if( c == 'a' )
		{QS=3; return( "even a" ); Q3:; }
	  else { QS=4; resume("not an a"); Q4:; }
	}
}
A complete program is in
	http://www.csci.csusb.edu/dick/cs320/c++/odda.cpp

By the way... it's possible to optimize the above code... but this is
not worth the effort in this and most cases.