The Department of Computer Science & Engineering |
STUART C. SHAPIRO: CSE
305
|
Fortran has a special statement for calling a procedure, CALL
Sub [(<argument_list>)]
. Other languages allow a procedure
call to be a statement by itself. C-based languages allow a function
call to be a statement---its value is discarded.
If a procedure subprogram has no parameters, Fortran allows just
the name to appear in the CALL
statement. Other
languages require the parentheses, even with nothing between them.
A call to a Prolog relational subprogram consists of the name of the relation and a list of argument expressions, which may have variables in them.
<wasat:Programs:2:296> sicstus SICStus 3.9.1 (sparc-solaris-5.7): Thu Jun 27 22:58:18 MET DST 2002 Licensed to cse.buffalo.edu | ?- [user]. % consulting user... | max(X,Y,Z) :- X>Y, Z=X; Z=Y. | printmax(X,Y) :- max(X,Y,Z), format("The max of ~d and ~d is ~d.~n", [X,Y,Z]). | % consulted user in module user, 10 msec 784 bytes yes | ?- printmax(3,5). The max of 3 and 5 is 5. yes | ?- printmax(7,2). The max of 7 and 2 is 7. yes
The header can sometimes appear without the body if the compiler needs the header information to compile calls, but the programmer does not want to provide the body details yet.
In some old programming languages, the body did not provide a separate binding environment, just an entry point and a way to specify return of execution to the caller.
SNOBOL had a dynamically-defined body. There was an entry point, and a local binding environment, but termination was determined by the dynamically next return goto to be executed.
A procedure may return when execution falls through to the bottom. I.e. the body is syntactically one statement; the procedure returns when that statement has been executed.
A return
statement may be used. It is an executable
statement whose effect is to terminate the procedure and return
execution to the calling unit. return
is a variety of
exit
that exits a procedure.
Fortran (77 & earlier) allows the name of a function subprogram to be used as a local variable within its body. The name's value at the time of termination is the value returned by the function.
Pascal also used this technique. However, since Pascal allowed functions to be recursive, using the function name where it would be evaluated for an l-value caused an error: "attempt to call a function with incorrect number of arguments".
Fortran 90 (and later) allows recursive functions, but only if declared so. In that case, the variable whose value is to be used as the result must also be declared:
Also notice theProgram factTest Integer i, fact Do i = 1, 4 Print *, i, fact(i) EndDo End Recursive Function fact(n) Result(result) Integer result, n If (n .eq. 0) Then result = 1 Else result = n * fact(n-1) Endif End -------------------------------------------------- <cirrus:Programs:1:128> f90 -o factTest.fout factTest.f <cirrus:Programs:1:129> factTest.fout 1 1 2 2 3 6 4 24
EndDo
in Fortran 90.
Many current languages allow return <expression>
.
This causes the function to terminate, and the value of
<expression>
becomes the value of the function.
cl-user(1): ((lambda (x y) (if (>= x y) x y)) 3 6) 6
The subprogram name follows the syntactic rules for an identifier, and, in statically scoped languages, has a scope, which is the enclosing block.
Procedures don't return a value, so usually don't have a type.
In C-based languages, a procedure is a function whose type is
void
.
In SNOBOL there could be more actual parameters than formal parameters. The extra actual parameters were matched to the local variables in order as though they were additional formal parameters.
In Common Lisp optional formal parameters are declared as such:
Common Lisp also allows for a sequence of optional arguments to be gathered into a list:cl-user(6): (defun reverse (list &optional end) (if (endp list) end (reverse (rest list) (cons (first list) end)))) reverse cl-user(7): (reverse '(a b c d e)) (e d c b a)
Perl uses this technique as its only method of parameter matching:cl-user(12): (defun union* (set1 set2 &rest sets) (let ((result (union set1 set2))) (dolist (set sets result) (setf result (union result set))))) union* cl-user(13): (union* '(a b c d) '(c e f g) '(a g h i) '(f j k)) (i h g a d b c e f j k)
#! /util/bin/perl sub test { print @_[0], @_[1], @_[2], "\n"; } test("a", "b", "c"); ------------------------------------- <cirrus:Programs:1:107> perl parmMatch.perl abc
Also see membercl-user(14): (defun testKey (&key first second third) (list first second third)) testKey cl-user(15): (testKey :second 2 :third 'c :first 'I) (I 2 c)
cl-user(16): (member 'c '(a b c d e)) (c d e) cl-user(17): (member '(3 c) '((1 a) (2 b) (3 c) (4 d) (5 e))) nil cl-user(18): (member '(3 c) '((1 a) (2 b) (3 c) (4 d) (5 e)) :test #'equal) ((3 c) (4 d) (5 e)) cl-user(19): (member 'March '((January 31) (February 28) (March 31) (April 30)) :key #'first) ((March 31) (April 30))
In Common Lisp:
cl-user(6): (defun packem (w x &optional (y 5) (z 7)) (list w x y z)) packem cl-user(7): (packem 1 3) (1 3 5 7) cl-user(8): (packem 1 3 11) (1 3 11 7)
In Python, optional parameters are indicated by supplying default
values; any parameters may be passed in keyword fashion;
*arg
collects extra arguments.
By interchanging the values of the formal parameters, subroutineProgram cbr Integer i, j i = 3 j = 5 Print *, "Before call, i = ", i, " j = ", j Call Swap(i, j) Print *, "After call, i = ", i, " j = ", j End Subroutine Swap(x, y) Integer x, y, temp temp = x x = y y = temp End ---------------------------------------------------- <cirrus:Programs:1:112> f95 -o cbr.fout cbr.f <cirrus:Programs:1:113> cbr.fout Before call, i = 3 j = 5 After call, i = 5 j = 3
Swap
interchanged the values of the actual parameters.
In Fortran IV, call-by-reference could be used to change the values of literal constants! Later versions of Fortran prevent this.
This figure shows the matching variables of that last example, and how information flows to instantiate them:append([], List2, List2). append([First | Rest], List2, [First | Intermediate]) :- append(Rest, List2, Intermediate). ------------------------------------------------------------ <wasat:Programs:1:103> sicstus SICStus 3.9.1 (sparc-solaris-5.7): Thu Jun 27 22:58:18 MET DST 2002 Licensed to cse.buffalo.edu | ?- consult('append.prolog'). % consulting /u0/faculty/shapiro/CSE305/Programs/append.prolog... % consulted /u0/faculty/shapiro/CSE305/Programs/append.prolog in module user, 0 msec 392 bytes yes | ?- append([a, b, c], [d, e, f], X). X = [a,b,c,d,e,f] ? yes | ?- append(X, [d, e, f], [a, b, c, d, e, f]). X = [a,b,c] ? yes | ?- append([a, b, c], X, [a, b, c, d, e, f]). X = [d,e,f] ? yes | ?- append([a, Second], [c,d,e,f], [First, b | Rest]). Rest = [c,d,e,f], First = a, Second = b ? yes | ?- append([X, b, [c], d], [e, X , Y], [a, b, [Y] | Z]). X = a, Y = c, Z = [d,e,a,c] ? yes
public class Name { public String first, last; public Name (String f, String l){ first = f; last = l; } public String toString() { return first + " " + last; } public static void ReName(Name n, String f, String l) { n.first = f; n.last = l; } public static void main (String[] args) { Name tom = new Name("Tom", "Thumb"); System.out.println("Tom's name is " + tom); ReName(tom, "Betty", "Boop"); System.out.println("Tom's name is " + tom); } // end of main () }// Name ---------------------------------------------------- <cirrus:Programs:1:122> javac Name.java <cirrus:Programs:1:123> java Name Tom's name is Tom Thumb Tom's name is Betty Boop
It's sometimes said that C uses pass-by-value except for arrays, which are passed by reference. But that's not true. C always uses pass-by-value. However, array-valued variables are actually pointer variables that point to the first element of the array. So the situation is like the situation in Java when passing reference variables. C's array is essentially passed by reference because the parameter is really passed by value:
#include <stdio.h> void swap(int b[]) { int temp; printf("b = %d\n", b); temp = b[0]; b[0] = b[1]; b[1] = temp; } int main() { int a[2] = {3, 5}; printf("a = %d\n", a); printf("Before call, a = [%d, %d]\n", a[0], a[1]); swap(a); printf("After call, a = [%d, %d]\n", a[0], a[1]); return 0; } ----------------------------------------------------- <wasat:Programs:1:132> gcc -Wall cbv.c -o cbv.out cbv.c: In function `swap': cbv.c:5: warning: int format, pointer arg (arg 2) cbv.c: In function `main': cbv.c:13: warning: int format, pointer arg (arg 2) <wasat:Programs:1:133> cbv.out a = -4264512 Before call, a = [3, 5] b = -4264512 After call, a = [5, 3]
In C++, an object-valued variable is bound to enough memory on the stack to hold the entire object. When such a variable is passed by value, the actual object cannot be changed by the subprogram:
#include <iostream> #include <string> using namespace std; class Name { public: string first, last; Name (string f, string l){ first = f; last = l; } string toString() { return first + " " + last; } }; void ReName(Name n, string f, string l) { n.first = f; n.last = l; } int main () { Name tom("Tom", "Thumb"); cout << "Tom's name is " << tom.toString() << endl; ReName(tom, "Betty", "Boop"); cout << "Tom's name is " << tom.toString() << endl; return 0; } ------------------------------------------------------------ <wasat:Programs:1:154> g++ Name.cc -o Name.ccout -R /util/gnu/lib <wasat:Programs:1:155> Name.ccout Tom's name is Tom Thumb Tom's name is Tom Thumb
Alternatively, the C++ object can be allocated on the heap, and a reference to it can be assigned to a pointer variable. Passing this variable by value has the same effect as passing a reference variable by value in Java:
#include <iostream> #include <string> using namespace std; class Name { public: string first, last; Name (string f, string l){ first = f; last = l; } string toString() { return first + " " + last; } }; void ReName(Name* n, string f, string l) { n->first = f; n->last = l; } int main () { Name* tom = new Name("Tom", "Thumb"); cout << "Tom's name is " << tom->toString() << endl; ReName(tom, "Betty", "Boop"); cout << "Tom's name is " << tom->toString() << endl; return 0; } ---------------------------------------------------------- <wasat:Programs:1:157> g++ cbp.cc -o cbp.ccout -R /util/gnu/lib <wasat:Programs:1:158> cbp.ccout Tom's name is Tom Thumb Tom's name is Betty Boop
C++ has reference variables and reference types. A C++ reference variable is a constant pointer variable that is implicitly dereferenced. If a formal parameter is a reference variable, it is bound to the address of the actual parameter, giving the same effect as Fortran's call-by-reference:
#include <iostream> using namespace std; void swap(int& x, int& y) { int temp; temp = x; x = y; y = temp; } int main() { int i, j; i = 3; j = 5; cout << "Before call, i = " << i << " j = " << j << endl; swap(i, j); cout << "After call, i = " << i << " j = " << j << endl; } -------------------------------------------- <wasat:Programs:1:160> g++ cbr.cc -o cbr.ccout -R /util/gnu/lib <wasat:Programs:1:161> cbr.ccout Before call, i = 3 j = 5 After call, i = 5 j = 3
We already saw one example: the Common Lisp function member
can be passed different test
and different keys to use.
Common Lisp and other functional languages include function as one of their data types. Therefore functions can be passed as parameters as easily as other data types. Common Lisp also has functions that apply functions to their arguments:
Here's a function that prints a table of a list of numbers and the results of applying some function to each of those numbers:cl-user(32): (type-of #'+) compiled-function cl-user(33): (funcall #'+ 1 2 3 4) 10 cl-user(34): (apply #'+ '(1 2 3 4)) 10 cl-user(35): (apply #'+ 1 2 '(3 4)) 10 cl-user(43): (type-of #'(lambda (x) (* x x))) function cl-user(44): (funcall #'(lambda (x) (* x x)) 3) 9
Here's the Python version:cl-user(45): (defun double (x) (+ x x)) double cl-user(46): (defun sqr (x) (* x x)) sqr cl-user(47): (defun printTable (f numbers) (loop for x in numbers do (format t "~d ~d~%" x (funcall f x)))) printTable cl-user(48): (printTable #'double '(1 2 3 4 5)) 1 2 2 4 3 6 4 8 5 10 nil cl-user(49): (printTable #'sqr '(1 2 3 4 5)) 1 1 2 4 3 9 4 16 5 25 nil
In C, an actual parameter may be the name of a function; the matching formal parameter must be a pointer to a function returning the correct type:def doubleit(x): return 2*x def sqr(x): return x*x def printTable(f, args): for i in args: print i, apply(f, (i,)) printTable(doubleit, [1,2,3,4,5]) print printTable(sqr, [1,2,3,4,5]) --------------------------------------- <wasat:Programs:2:153> python printTable.py 1 2 2 4 3 6 4 8 5 10 1 1 2 4 3 9 4 16 5 25
#include <stdio.h> int doubleit(int x) { return x + x; } int sqr(int x) { return x * x; } void printTable(int (*f)(), int a[], int length) { int i; for (i = 0; i < length; i++) { printf("%d %d\n", a[i], (*f)(a[i])); } } int main() { int a[5] = {1, 2, 3, 4, 5}; printTable(doubleit, a, 5); printf("\n"); printTable(sqr, a, 5); return 0; } ------------------------------------------------------- <wasat:Programs:1:203> gcc -Wall funcall.c -o funcall.out <wasat:Programs:1:204> funcall.out 1 2 2 4 3 6 4 8 5 10 1 1 2 4 3 9 4 16 5 25
A generic subprogram uses basically the same procedure on different types of actual parameters, possibly with some minor changes. For example a sort procedure that can sort a collection of any type of element as long as that type has a "before" method. The details of how before works may differ from type to type, but the sort procedure is the same.
Some languages, e.g. C++, allow the programmer to provide new procedures for the languages operators (+, *, etc.), thus further overloading them.
A coroutine is a subprogram that is an exception to the characteristic that a subprogram is called by a caller, executes until it terminates, and then returns control to its caller. A coroutine may temporarily give control back to its caller, or to another coroutine, and later be resumed from where it left off. A set of coroutines may pass control among themselves, each resuming each time from where it left off.
Read Sebesta.