Parameter Passing Semantics: Fall 21
Goal: Explore various different parameter passing schemes and typing of
functions.
What will be covered ?
- The semantics of different parameter passing mechanisms
- reference memory model vs value memory model
- Named parameters
- Diagram
Subprograms/subroutines/functions
and
Parameters
Ada Example:
PROCEDURE max (x,y : in INTEGER; maximum : out INTEGER) is
BEGIN
IF x > y THEN
ELSE
END IF;
END max;
FUNCTION max1 (x,y : in INTEGER) return INTEGER is
BEGIN
IF x > y THEN
ELSE
END IF;
END max1;
Reference Memory vs Value model:
Parameter Passing Mechanisms
One of the most important design decisions is which parameter passing
methods are implemented
Pass by Value (Pass by assignment,
Pass by sharing)
Pass by Result
Pass by Value Result (also called Copy in, Copy out)
Pass by Reference
Pass by Name
Pass by Value (Pass by "assignment")
- Default mechanism in Pascal, Modula-2, Ada and C#
- Main mechanism in C and Algol 68, Swift and Objective C
- Behavior depends on whether the language uses a value
model or reference model.
- Only mechanism in Java and Python
- immutable vs mutable objects
Pascal,
Java and Modula-2 parameters are treated like initialized local
variables
Assignments to the parameters do not cause changes outside the
subprogram
Ada: The in parameter may not be assigned to
Java: The keyword final prevents assigning to the parameter
at all.
What kinds of methods can not be easily coded?
- Swap -- Can a subroutine be written to exchange the value of 2
parameter?
Evaluation
of parameters
Order of evaluation of parameters with side effects can be resolved
by insisting on an order in evaluating (right to left or left to
right).
Java enforces the above
Most languages the evaluation order is implementation dependent.
Parameter
Evaluation and Side Effects
Try on a variety of C and C++ compilers:
y = -1;
upOnly(++y, ++y);
Some implementations evaluate the first parameter (and modify
it) before the second, others evaluate the last parameter first.
Some language definitions make this dependency an error.
Pass by Reference
program Test();
var innocent :integer;
procedure modify(var Share: integer);
begin
Share := Share + 3;
end;
begin
innocent := 100;
modify(innocent) {upon return innocent is 103}
end;
- The argument's allocated location is bound to the parameter
during the call.
The parameter becomes an alias for
the argument.
- Therefore any changes to the parameter will have affects
outside the called subprogram.
- Arguments must have l-values.
- This is the only mechanism in Fortran 77 and earlier.
- Pascal, Modula 2 use the keyword VAR to signal pass by
reference
- C# use the keyword ref to
signal
pass
by reference
Simulation of pass by reference
C & Algol68: A pointer is used to pass a location explicitly.
In C: pass by value (assignment)
a pointer type.
void refer (int
*x) {
*x += 1;
}
int main() {
}
- C: Arrays are passed with the same semantics as reference
"types"
are passed in Java.
- int passArr(
int [] a) {
-
...
- }
- C++ has introduced explicit "pass-by-reference" by introducing
a reference type --
alias --
void refer(int& x){
x += 1;
}
void fun() {
}
- C++: The ref type can be used other in locations :
void f() {
int i;
int &r = i; // r refers to i
r = 9; // the value i becomes is 9
int * p = &i; // p points to i
int & rr = r ; // rr refers to i
}
(Ellis & Stroustup,Annotated C++, page 153)
procedure swap (var x: integer,
var
y: integer);
}
- Early versions of Fortran did not check if the actual parameter
was assignable.
- Calling swap(1,2) would result in the constants being
interchanged. Later the code would calculate 1+1 = 4.
R-Value
references & move constructor c++
11
Pass by Value-Result
- Also called COPY-IN, COPY-OUT.
- In Ada, this is the mechanism of the IN OUT parameter for
scalar types
- In Swift the keyword is inout
- Aliasing distinguishes pass by value-result from pass by
reference
a : INTEGER;
PROCEDURE p(x,y: IN OUT INTEGER) is
Assuming pass by reference what is the last value of "a"?
Assuming pass by Copy-in Copy-out what is the last value of "a"?
Issues left unspecified
- Order the results are copied back
- Are the locations of arguments stored or recalculated on
return?
- ADA Quirk: Ada's definition states that in out parameters
may be implemented as pass by reference or pass by value-result
for non-scalar types.
Another Swap :
PROCEDURE Main IS
PROCEDURE Swap(x,y: IN OUT integer)
is
BEGIN
x := x + y;
y := x - y;
x := x - y;
END;
BEGIN
a := 1;
Swap(a,a);
END;
PROCEDURE MAIN
PROCEDURE Swap(VAR x,y: integer);
BEGIN
x := x + y ;
y := x - y ;
x := x - y ;
END;
BEGIN
a:= 1;
Swap(a,a)
END
- What is the last value of a after each is executed?
- What is the value of "a" after p is called? (Ada 1, Pascal 0)
- Another difference:
- If the subprogram terminates abnormally (i.e.. via an
exception handler) the actual parameter
- might have changed value using the call by reference.
- but actual parameter will not change using call by
value-result
Pass by Name
- Used in Algol 60
- Haskell implements a variation -- Lazy evaluation
- Arguments are not evaluated until their actual use in the
called program.
- Example:
int i;
int a[ ] = new int[2];
void p(x) { //pass by name
i = i + 1;
x = x + 1;
}
void main() {
i = 0;
a[0] = 0;
a[1] = 1;
p( a[i] );
}
- To "hand execute" pass by name,
just substitute the actual parameters textually in place of the
formal parameters.
In fact, each reference to a call by name reference is implemented
by
jump out to the argument
(called a "thunk" )
execute the argument expression
jump back into the routine
Thus the above would be
in main: i = 0;
a[0]
= 0;
a[1] = 1;
in p(x):
i = i + 1;
a[i] = a[i] +1;
- Unlike other languages, this code modifies a[1], not a[0]!
- Jensen's
Device is an example showing the power of pass by name.
Functional Languages and Pass by Name
- Since functional languages do not have state changes a variation of
Pass by Name, Lazy evaluation, is an excellent mechanism for passing
variables.
- Lazy evaluation evaluates each argument at most once.
- This the main mechanism in Haskell.
- This mechanism enables new and interesting programming
abstraction.
- In non pure functional languages call by name is hard to implement.
- If in evaluating the parameter, the parameter is a functions
with side effects, you may effect in unexpected ways the value of a
variable in later use.
- The semantics of call by name is very difficult to very messy to
implement.
Macro expansion vs. procedure "pass by name"
- Similarity: both "employ" textural substitution
- For procedural calls substitution is theoretical model how the
parameter bind.
- For macro expansion the textural substitution is the actual
implementation
- The #define preprocessor command (in C ) causes
an identifier name to become defined as macro to preprocessor.
- function-like macro definition:
#define name ( name1,
name2, ...) body
name ( arg1, arg2
...)
- example: If a macro sum with two arguments is defined by
#define sum(x,y) x+y
- then the preprocessor replaces the source line (before compiling)
result = sum ( a, 3*a +b );
with
result = a + 3*a +b;
- substitute text of macro call's argument for each occurrence of nameX
in body
- substitute resulting body for the macro call
- Swap Problem 5 : macro / call by name
#define swap(a,b) { int t; t=a;
a=b; b=t; }
then
swap( i, A[i] ) ;
becomes
{ int t; t=i; i=A[i]; A[i]=t; };
Both call by name and macro have a "side effect" problem -- changing i's
r-value
changes A[i] l-value.
- Difference between call by name and macros:
float e =2.71828;
#define p(x) (x/e)
...
int f (int e ) {
...
p(z);
...
}
- This example creates a name conflict -- the compiler sees:
float e =2.71828;
...
int f (int e ) {
...
(z/e); /* DIVIDES the WRONG "e" */
...
}
- Macro expansion imports e into the scope of a different
binding of e -- a case of dynamic scoping.
- Naming problems can be avoided by true call by name parameter binding.
Some additional variations : Default Parameter Values
Conformant Arrays
Variable number of arguments
int get_word(char*, int &, int
start = 0);
- C: Parameter names are not needed in function prototype
TYPE intptr is ACCESS integer;
FUNCTION Get_word(C
: String; X : intptr; Start : integer := 0 ) RETURN Integer;
Both languages parameters with default values can be omitted
- C++ needs the default parameters places at the end of the list of
parameters
Otherwise, function calls would be ambiguous
- Ada allows named parameter passing
V := Get_word( Start => 1,C => "abc",
X => new
integer(1)
);
The existence of named parameter passing
forces Ada to require that parameter names be provided in the subprogram
declaration.
- Python also allows default parameters and named parameters
def
func(spam, eggs, toast=0, ham=0):
print (spam,eggs,toast,ham)
func(1,2)
OR
func(1,2, ham=1,toast=0)
Or
func(1,
eggs=99)
Or
func(1,2,3,4)