The Department of Computer Science & Engineering
|
|
STUART C. SHAPIRO: CSE
305
|
Fortran has a special statement for calling a procedure, CALL
Sub [([<argument> {,<argument}]>)]. Other languages allow a procedure
call to be a statement by itself.
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.
<timberlake:Test:1:76> prolog
SICStus 4.0.5 (x86_64-linux-glibc2.3): Thu Feb 12 09:48:30 CET 2009
Licensed to SP4cse.buffalo.edu
| ?- [user].
% compiling 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]).
|
% compiled user in module user, 0 msec 1264 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 r-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:
Program 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
--------------------------------------------------
<timberlake:Test:1:164> f95 -o factTest factTest.f
<timberlake:Test:1:165> ./factTest
1 1
2 2
3 6
4 24
Also notice the EndDo instead of a label.
Many current languages allow return <expression>.
This causes the function to terminate, and the value of
<expression> becomes the value of the function.
In Python and Ruby multiple return values are specified by
following return with several expressions separated
by commas. For example in Python:
Actually, they package their multiple values in collections, a tuple for Python, an array for Ruby,>>> def quoRem(x,y): ... return x/y, x%y ... >>> q,r = quoRem(10,3) >>> q 3 >>> r 1
The LHS of the assignment statement shown above is just an instance of parallel assignment.>>> quoRem(10,3) (3, 1) >>> def printit(x): ... print x ... >>> printit(quoRem(10,3)) (3, 1)
Python's functions may return no value:
>>> def nothing(): ... return ... >>> nothing() >>>
<timberlake:Test:1:184> irb irb(main):001:0> def nothing() irb(main):002:1> return irb(main):003:1> end => nil irb(main):004:0> nothing() => nil
Common Lisp functions return multiple values via the
values function:
The values may be stored using one of the functionscl-user(147): (defun quoRem (x y) (values (floor (/ x y)) (mod x y))) quoRem cl-user(148): (quoRem 10 3) 3 1
multiple-value-bind,
multiple-value-call,
multiple-value-list,
multiple-value-prog1, or
multiple-value-setq. For example,
Common Lisp does not package the multiple values in a collection, and if used in a context that only expects one value, all but the first are ignored:cl-user(149): (multiple-value-bind (q r) (quoRem 10 3) (format t "The quotient of 10 and 3 is ~d and the remainder is ~d~%" q r)) The quotient of 10 and 3 is 3 and the remainder is 1 nil
Like Python, a Common Lisp function may return no value:cl-user(150): (print (quoRem 10 3)) 3 3 cl-user(151):
A good use of multiple values is retrieval from a hashmap. The first value can be the value found, orcl-user(151): (defun nothing () (values)) nothing cl-user(152): (nothing) cl-user(153):
nil if the key wasn't
stored. However, this value of nil then does not
distinguish between nothing being found and nil being
found. So the second value can be True or False, indicating whether
the lookup was successful.
A good use for returning no value is for a function that prints information to the user, in order not to confuse that value printed with the value returned:
cl-user(153): (defun justPrint (x) (print x) (values)) justPrint cl-user(154): (justPrint (quoRem 10 3)) 3 cl-user(155):
>>> (lambda x: x+1)(3) 4
The subprogram name, when it has one, 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)
Common Lisp optional and rest parameters, if not matched with actual parameters, are initialized, like local variables, tocl-user(5): (defun union* (set &rest sets) (loop with result = set for next in sets do (setf result (union next result)) finally (return result))) union* cl-user(6): (union* '(a b c) '(c d e) '(e f g) '(h i j)) (j i h g f e d a b c)
nil. (But see the discussion of default values, below.)
Perl uses the technique of matching a sequence of actual parameters to one array as its only method of parameter matching:
#! /util/bin/perl
sub test {
print @_[0], @_[1], @_[2], "\n";
}
test("a", "b", "c");
-------------------------------------
<timberlake:Test:1:28> perl paramMatch.perl
abc
Java uses "varargs" to collect extra arguments:
public class RestArg {
public static void test(String... strings) {
for (String str : strings) {
System.out.println(str);
}
}
public static void main(String[] args) {
test("One string", "Second string", "Third string");
}
}
-----------------------------------------------------------------
<timberlake:Test:1:36> javac RestArg.java
<timberlake:Test:1:37> java RestArg
One string
Second string
Third string
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.
>>> def list(x,y=3): ... return x,y ... >>> list(6,7) (6, 7) >>> list(2) (2, 3) >>> list(y=5,x=4) (4, 5) >>> def listmore(x,*y): ... return x,y ... >>> listmore(1,2,3,4,5,6) (1, (2, 3, 4, 5, 6)) >>>
In Ruby, optional parameters are also indicated by supplying
default values, and *arg collects extra arguments, but
keyword parameters are not supported.
<timberlake:Test:1:29> irb irb(main):001:0> def list(x,y=3) irb(main):002:1> return x,y irb(main):003:1> end => nil irb(main):004:0> list(6,7) => [6, 7] irb(main):005:0> list(2) => [2, 3] irb(main):006:0> def listmore(x,*y) irb(main):007:1> return x,y irb(main):008:1> end => nil irb(main):009:0> listmore(1,2,3,4,5,6) => [1, [2, 3, 4, 5, 6]]
Program 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
----------------------------------------------------
<timberlake:Test:1:38> f95 -o cbr.fout cbr.f
<timberlake:Test:1:39> ./cbr.fout
Before call, i = 3 j = 5
After call, i = 5 j = 3
By interchanging the values of the formal parameters, subroutine
Swap interchanged the values of the actual parameters.
A Common Lisp macro is a subprogram that
Many "functions" in Common Lisp are actually macros defined in Common Lisp itself.
ANSI Common Lisp has no while loop, but the Allegro Common Lisp
extension defines while
as a macro:
Let's try it:(defmacro while (condition &rest forms) `(loop (unless ,condition (return)) ,@forms))
The backquote character is new:cl-user(51): (let ((i 1)) (while (< i 5) (print i) (incf i))) 1 2 3 4 nil
Normally, every element of a Lisp expression after the function name is evaluated unless quoted:Look at the definition and use ofThe backquote is like the normal quote:cl-user(55): (list 'a 'b (+ 3 2) 'c '(list d e) 'f) (a b 5 c (list d e) f) cl-user(56): '(a b (+ 3 2) c (list d e) f) (a b (+ 3 2) c (list d e) f)But anything preceded by a comma is "unquoted"---evaluatedcl-user(57): `(a b (+ 3 2) c (list d e) f) (a b (+ 3 2) c (list d e) f)If comma precedes a form that will evaluate to a list, putting an "cl-user(60): `(a b ,(+ 3 2) c ,(list 'd 'e) f) (a b 5 c (d e) f)@" after the comma "splices" the list in. (Think Ruby's splat.)cl-user(61): `(a b ,(+ 3 2) c ,@(list 'd 'e) f) (a b 5 c d e f)
while again:
Common Lisp's(defmacro while (condition &rest forms) `(loop (unless ,condition (return)) ,@forms)) cl-user(51): (let ((i 1)) (while (< i 5) (print i) (incf i))) 1 2 3 4 nil
macroexpand let's us see the form that the
macro returns before it is evaluated again:
cl-user(63): (macroexpand '(while (< i 5) (print i) (incf i)))
(block nil
(tagbody
#:g319
(progn (unless (< i 5) (return)) (print i) (incf i))
(go #:g319)))
t
Notice how the actual argument expressions were bound as values of the
formal parameters, and appear in the resulting form.
A problem with pass-by-name occurs when a variable in the actual argument expression is the same as a variable in the code where it is used. Here is an example based on Paul Graham, ANSI Common Lisp, Prentice Hall, Englewood Cliffs, NJ, 1996, p. 165-168.
However:cl-user(99): (defmacro ntimes (n &body forms) `(loop for x from 1 to ,n do ,@forms)) ntimes cl-user(100): (ntimes 5 (princ ".") (princ "!")) .!.!.!.!.! nil cl-user(101): (let ((y 10)) (ntimes 5 (incf y)) y) 15
We can see the problem if we usecl-user(102): (let ((x 10)) (ntimes 5 (incf x)) x) 10
macroexpand:
cl-user(103): (macroexpand '(ntimes 5 (incf x)))
(block nil
(let ((x 1))
(declare (type real x))
(tagbody
excl::next-loop
(incf x)
(excl::loop-really-desetq x (1+ x))
(when (> x '5) (go excl::end-loop))
(go excl::next-loop)
excl::end-loop)))
t
Putting that code inside the let, we see the problem more
clearly:
(let ((x 10))
(block nil
(let ((x 1))
(declare (type real x))
(tagbody
excl::next-loop
(incf x)
(excl::loop-really-desetq x (1+ x))
(when (> x '5) (go excl::end-loop))
(go excl::next-loop)
excl::end-loop)))
x)
The x of the macro shadowed he x of the
user's let block.
The solution is to use gensym:
cl-user(104): (defmacro ntimes (n &body forms)
`(loop for ,(gensym) from 1 to ,n
do ,@forms))
ntimes
cl-user(105): (let ((x 10)) (ntimes 5 (incf x)) x)
15
cl-user(106): (macroexpand '(ntimes 5 (incf x)))
(block nil
(let ((#:g356 1))
(declare (type real #:g356))
(tagbody
excl::next-loop
(incf x)
(excl::loop-really-desetq #:g356 (1+ #:g356))
(when (> #:g356 '5) (go excl::end-loop))
(go excl::next-loop)
excl::end-loop)))
t
Here's an example in C#:
"In C#, arguments can be passed to parameters either by value or by
reference... To pass a parameter by reference, use the ref or out
keyword." [Passing
Parameters (C# Programming Guide)] We will examine the issue of
whether out parameters are really pass-by-reference
below.
// C# comparison of pass-by-value vs. pass-by-result
// Copied from http://msdn.microsoft.com/en-us/library/0f66670z.aspx
// and slightly modified.
using System;
class valueVsResult
{
static void Main(string[] args)
{
int x, x2;
// Passing by value.
// The value of x2 in Main is not changed.
x = x2 = 4;
squareVal(x,x2);
Console.WriteLine("After pass-by-value x2 = " + x2);
// Output: 4
// Passing by result
// The value of x2 in Main is changed.
x = x2 = 4;
squareOut(x, out x2);
Console.WriteLine("After pass-by-result x2 = " + x2);
// Output: 16
}
static void squareVal(int inParameter, int outParameter)
{
outParameter = inParameter * inParameter;
}
// Passing by reference
static void squareOut(int inParameter, out int outParameter)
{
outParameter = inParameter * inParameter;
}
}
-------------------------------------------------------------
<timberlake:Test:1:172> mcs valueVsResult.cs
<timberlake:Test:1:173> mono valueVsResult.exe
After pass-by-value x2 = 4
After pass-by-result x2 = 16
In Fortran IV, pass-by-reference could be used to change the values of literal constants! Later versions of Fortran prevent this.
We can test for pass-by-reference as in the following Fortran program:
Program pbrTest
Integer i, j
i = 3
j = 5
Print 10, i,j
10 Format(" Before call, i = ", i2, " j = ", i2)
Call testPBR(i, i)
Print 20, i,j
20 Format(" After call, i = ", i2, " j = ", i2)
End
Subroutine testPBR(x, y)
Integer x, y
Print 30, x,y
30 Format(" testPBR called with x = ", i2, " y = ", i2)
y = x*x
Print 40, x,y
40 Format(" testPBR about to return with x = ", i2, " y = ", i2)
End
------------------------------------------------------
<timberlake:Test:1:113> f95 -o pbrTest pbrTest.f
<timberlake:Test:1:114> ./pbrTest
Before call, i = 3 j = 5
testPBR called with x = 3 y = 3
testPBR about to return with x = 9 y = 9
After call, i = 9 j = 5
Notice that since testPBR was called with both actual
arguments being the same, the two formal parameters of
testPBR were aliases.
C++ also has pass-by-reference:
#include <stdio.h>
void testPBR(int &x, int &y) {
printf("testPBR called with x = %d y = %d\n", x, y);
y = x*x;
printf("testPBR about to return with x = %d y = %d\n", x, y);
}
int main(int argc, char **argv) {
int i=3, j=5;
printf("Before call, i = %d j = %d\n", i, j);
testPBR(i,i);
printf("After call, i = %d j = %d\n", i, j);
return 0;
}
------------------------------------------------------------
<timberlake:Test:1:117> g++ -Wall -o pbrTestCPP pbrTest.cpp
<timberlake:Test:1:118> ./pbrTestCPP
Before call, i = 3 j = 5
testPBR called with x = 3 y = 3
testPBR about to return with x = 9 y = 9
After call, i = 9 j = 5
So let's see if out parameters in C# are really
pass-by-result or if they are pass-by-reference:
using System;
class pbrTest
{
static void Main(string[] args)
{
Console.WriteLine("C# out pbrTest");
int i,j;
i=3;
j=5;
Console.WriteLine("Before call, i = " + i + " j = " + j);
testPBR(out i, out i);
Console.WriteLine("After call, i = " + i + " j = " + j);
}
static void testPBR(out int x, out int y)
{
Console.WriteLine("testPBR called with x = " + x + " y = " + y);
y = x*x;
Console.WriteLine("testPBR about to return with x = "+ x + " y = " + y);
}
}
------------------------------------------------------------------
<timberlake:Test:1:136> mcs pbrTest.cs
pbrTest.cs(19,64): error CS0269: Use of unassigned out parameter `x'
pbrTest.cs(20,11): error CS0269: Use of unassigned out parameter `x'
pbrTest.cs(21,73): error CS0269: Use of unassigned out parameter `x'
Compilation failed: 3 error(s), 0 warnings
So x did not use in-mode or inout-mode and was given its
own memory address. So, for this C# compiler at least, out
parameter passing is pass-by-result.
Now let's try ref parameters instead:
using System;
class pbrTest
{
static void Main(string[] args)
{
Console.WriteLine("C# ref pbrTest");
int i,j;
i=3;
j=5;
Console.WriteLine("Before call, i = " + i + " j = " + j);
testPBR(ref i, ref i);
Console.WriteLine("After call, i = " + i + " j = " + j);
}
static void testPBR(ref int x, ref int y)
{
Console.WriteLine("testPBR called with x = " + x + " y = " + y);
y = x*x;
Console.WriteLine("testPBR about to return with x = "+ x + " y = " + y);
}
}
--------------------------------------------------------------------
<timberlake:Test:1:149> mcs pbrTest.cs
<timberlake:Test:1:150> mono pbrTest.exe
C# ref pbrTest
Before call, i = 3 j = 5
testPBR called with x = 3 y = 3
testPBR about to return with x = 9 y = 9
After call, i = 9 j = 5
So we conclude that ref parameters implement inout-mode
semantics, and use the addresses of their actual arguments. Thus, for
this C# compiler at least, ref parameter passing is
pass-by-reference.
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
----------------------------------------------------
<timberlake:Test:1:174> javac Name.java
<timberlake:Test:1:175> 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 = %p\n", b);
temp = b[0];
b[0] = b[1];
b[1] = temp;
}
int main() {
int a[2] = {3, 5};
printf("a = %p\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;
}
-----------------------------------------------------
<timberlake:Test:1:184> gcc -Wall -o cbv cbv.c
<timberlake:Test:1:185> ./cbv
a = 0x7ffff1b2ace0
Before call, a = [3, 5]
b = 0x7ffff1b2ace0
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
All Prolog parameters are passed by inout-mode:
pair(X,Y,Z) :- Z = [X,Y].
showPair(E1,E2,Result) :- pair(E1,E2,Result),
format("The pair of ~w and ~w is ~w.~n", [E1,E2,Result]).
------------------------------------------------------------------
SICStus 4.0.5 (x86-linux-glibc2.3): Thu Feb 12 09:47:39 CET 2009
Licensed to SP4cse.buffalo.edu
| ?- ['pair.pro'].
% compiling /projects/shapiro/CSE305/Test/pair.pro...
% compiled /projects/shapiro/CSE305/Test/pair.pro in module user, 0 msec 648 bytes
yes
| ?- showPair(a,b,P).
The pair of a and b is [a,b].
P = [a,b] ?
yes
| ?- showPair(a,Y,[a,c]).
The pair of a and c is [a,c].
Y = c ?
yes
| ?- showPair(X,d,[a,d]).
The pair of a and d is [a,d].
X = a ?
yes
| ?- showPair(X,Y,[a,3]).
The pair of a and 3 is [a,3].
X = a,
Y = 3 ?
yes
| ?-
We already saw one example: the Common Lisp function member can be passed different tests
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
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])
---------------------------------------
<timberlake:Test:1:204> python printTable.py
1 2
2 4
3 6
4 8
5 10
1 1
2 4
3 9
4 16
5 25
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. "The name of a function is a constant pointer to the
function. Its value is the address of the function's machine code in
memory." [Peter Prinz and Ulla Kirch-Prinz, (Translated by Tony
Crawford), C
Pocket Reference, O'Reilly Media, 2002, p. 55.]
#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;
}
-------------------------------------------------------
<timberlake:Test:1:207> gcc -Wall -o printTable printTable.c
<timberlake:Test:1:208> ./printTable
1 2
2 4
3 6
4 8
5 10
1 1
2 4
3 9
4 16
5 25
As an example, consider the tail-recursive version of factorial in Python:
def factorial(x,result=1):
if x<=1:
return result
else:
return factorial(x-1,x*result)
---------------------------------------------
< python
Python 2.6.4 (r264:75706, Dec 21 2009, 12:37:31)
[GCC 4.1.2 20080704 (Red Hat 4.1.2-46)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import trFact
>>> trFact.factorial(1)
1
>>> trFact.factorial(4)
24
In Java, that is written:
class trFact {
public static int factorial(int x) {
return factorial(x,1);
}
public static int factorial(int x,int result) {
if (x<=1)
return result;
else
return factorial(x-1, x*result);
}
public static void main(String[] args) {
System.out.println("1! = " + factorial(1));
System.out.println("4! = " + factorial(4));
}
}
--------------------------------------------------------
<timberlake:Test:1:214> javac trFact.java
<timberlake:Test:1:215> java trFact
1! = 1
4! = 24
This will clearly cause an infinite loop if we callfactorial(0) = 1 factorial(n) = n * factorial(n-1) ----------------------------------------- <timberlake:Test:1:223> ghci GHCi, version 6.12.1: http://www.haskell.org/ghc/ :? for help Loading package ghc-prim ... linking ... done. Loading package integer-gmp ... linking ... done. Loading package base ... linking ... done. Loading package ffi-1.0 ... linking ... done. Prelude> :load factorial.hs [1 of 1] Compiling Main ( factorial.hs, interpreted ) Ok, modules loaded: Main. *Main> factorial(0) 1 *Main> factorial(4) 24 *Main> C-d Leaving GHCi.
factorial with an argument less than 0. So we can
add a guard:
Note that the error indicated a failure of pattern-matching, not a failure within the body of the function.factorial(0) = 1 factorial(n) | n>0 = n * factorial(n-1) ---------------------------------------------------------- Prelude> :load factorial2.hs [1 of 1] Compiling Main ( factorial2.hs, interpreted ) Ok, modules loaded: Main. *Main> factorial(0) 1 *Main> factorial(4) 24 *Main> factorial(-1) *** Exception: factorial2.hs:(1,0)-(3,20): Non-exhaustive patterns in function factorial
If we want to extend the function to negative arguments, we could add another guarded option:
factorial(0) = 1 factorial(n) | n<0 = -1 | n>0 = n * factorial(n-1) ------------------------------------------------ Prelude> :load factorial3.hs [1 of 1] Compiling Main ( factorial3.hs, interpreted ) Ok, modules loaded: Main. *Main> factorial(0) 1 *Main> factorial(4) 24 *Main> factorial(-1) -1
We could also use this technique to write our own implementation of
append:
append([],list) = list append(x:xs, list) = x : append(xs,list) -------------------------------------------------------- *Main> :load append.hs [1 of 1] Compiling Main ( append.hs, interpreted ) Ok, modules loaded: Main. *Main> append([],[1,2,3]) [1,2,3] *Main> append([1,2,3],[4,5,6]) [1,2,3,4,5,6]
Erlang's dispatching by pattern matching looks a lot like
Haskell's. Here's factorial for Erlang:
-module(factorial).
-export([factorial/1]).
factorial(0) -> 1;
factorial(N) when N<0 -> -1;
factorial(N) when N>0 -> N * factorial(N-1).
---------------------------------------------------
<timberlake:Test:1:238> erl
Erlang R13B03 (erts-5.7.4) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.7.4 (abort with ^G)
1> c(factorial).
{ok,factorial}
2> factorial:factorial(0).
1
3> factorial:factorial(4).
24
4> factorial:factorial(-1).
-1
5> q().
ok
6> <timberlake:Test:1:239>
Here's an Erlang implementation of append:
-module(append).
-export([append/2]).
append([],List2) ->
List2;
append([First|Rest],List2) ->
[First | append(Rest,List2)].
--------------------------------------------------
4> c(append).
{ok,append}
5> append:append([],[2,3,4]).
[2,3,4]
6> append:append([1,2],[2,3,4]).
[1,2,2,3,4]
7> append:append([a,b,c],[d,e,f]).
[a,b,c,d,e,f]
factorial in Prolog is similar to
that in Haskell and Erlang, except for the fact that Haskell and
Erlang are functional, whereas Prolog is relational:
Haskell and Erlang allow literals and variables in the parameter list of the function header, but no unbound variables in the function call.factorial(0,1). factorial(N,-1) :- N<0. factorial(N,F) :- N>0, X is N-1, factorial(X,Y), F is N*Y. ------------------------------------------------------------- | ?- ['factorial.pro']. % compiling /projects/shapiro/CSE305/Test/factorial.pro... % compiled /projects/shapiro/CSE305/Test/factorial.pro in module user, 0 msec -88 bytes yes | ?- factorial(0,X). X = 1 ? yes | ?- factorial(4,X). X = 24 ? yes | ?- factorial(-1,X). X = -1 ? yes
Prolog allows unbound variables in the function call as well as in the function header, just as it allows unbound variables in the RHS of assignment expressions. Pattern matching when variables are allowed in both expressions being matched is called "unification". Also recall that all of Prolog's parameter passing is by inout mode. Example:
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
This figure shows the matching variables of that last example, and how
information flows to instantiate them:
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.