Principles of Programming Languages and Systems

by
Peter Fritzson,

PELAB - Programming Environment Laboratory
Dept. of Computer and Information Science,
Linköping University, Sweden

Lecture 3

Parameter passing

(chapter 7, p 213-233)

Call by value

Arguments are evaluated before passing those to the function

Myplus[x_,y_] := Plus[x,y];

Myplus[2*3,10]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr1.gif]

Arguments are evaluated but not Foo3, since it is not defined

Foo3[2*3,10]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr3.gif]
Call by name

Esoteric evaluation model, first used in Algol60. Pass arguments unevaluated into the "text" of the function body, i.e. pure substitution.
This is theoretically sound, but in practice less attractive. A generalization is normal order evaluation used by lazy funtional languages.

Practical problems occur together with result parameters (which do not occur in functional languages):

SetAttributes[swap, HoldAll];

swap[a_, b_] := Module[{
temp
},
temp = a;
a = b;
b = temp;
];

SetAttributes[swap2, HoldAll];

swap2[a_, b_] := Module[{
temp
},
Hold[temp = a;
a = b;
b = temp]
];

Attributes[swap]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr4.gif]

a=p

temp=p; t[[p]]=

Try to use it

p=1; q = 2;

swap[p,q];
{p,q}
[Graphics:lecture3gr2.gif][Graphics:lecture3gr5.gif]

Working case

p =2;  t = {3,5,4};

swap[t[[p]], p];
{p,t}
[Graphics:lecture3gr2.gif][Graphics:lecture3gr6.gif]

Problematic case

p =2;  t = {3,1,4};

swap[p, t[[p]]];
{t,p}
[Graphics:lecture3gr2.gif][Graphics:lecture3gr7.gif]

p =2;  t = {3,1,4};

swap2[p, t[[p]]]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr8.gif]

It has been proven theoretically impossible to write a call by name swap function that works for all possible parameters. (Fleck 76).

Call by value-result

First evaluate and pass argument values. Then possibly pass back results to output parameters.

Call by reference

Call by references passes locations (= addresses) of variables, which makes it possible to pass back results.
This is equivalent to call by value-result in the absence of parameter aliasing.

Functional language parameter passing

Only call-by-value and call-by-name are used in functional languages, since these languages do not have output (i.e. result) parameters.
All results are returned as function values.

Functional Programming

(Chapter 10, p 358)

Off[General::"spell1"]
Functional programming languages and functional programming style:

Based of the mathematical concept of function

Functions are first-class values

This means that you can do the same with functions as with other values, i.e. they
can be passed as arguments, returned as values, etc.

All programs and procedures are functions and clearly distinguish incoming values (parameters) from outgoing values (results).

The are no ordinary variables - replaced by parameters

Variables are assigned at most once (single-assignment)

Loops are (usually) replaced by recursive calls

The value of a function depends only on its parameters, and not on evaluation order or execution path ("referential transparency")

Additional functional programming terms

Pure functional programming

No side effects

Referential transparency

A construct has the same meaning wherever it legally occurs

The gcd example again (p 358)

Conventional imperative programming with loops (p 361)

gcd[u_,v_] := Module[{
y,t,x
},
x = u; y = v;
While[y != 0,
t = y;
y = Mod[x,y];
x = t
];
Return[x]
];

gcd[8,16]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr9.gif]

Conventional function style (p 361)

gcd[u_,v_] := Block[{
},
If[v==0,
Return[u]
,
Return[gcd[v,Mod[u,v]]]
]
];

gcd[8,18]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr10.gif]

Shorter version (Scheme-like)

gcd[u_,v_] := 
If[v==0,
u
,
gcd[v, Mod[u,v]]
];

gcd[8,16]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr11.gif]

Rule version (p 361)
(recurrence equations)

gcd[u_,0]  := u;

gcd[u_,v_] := gcd[v, Mod[u,v]];

gcd[8,28]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr12.gif]
Three important properties of Lisp, Scheme, and Mathematica (p 368)

The uniform representation of programs and data using a single general data structure - the list (in Mathematica: the expression)

This enables: programs = data

The definition of the language using an interpreter written in the language itself - called a metacircular interpreter (Mathematica: a term-rewriting system)

The automatic management of all memory by the run-time system

Elements of Scheme and Mathematica (p 369)

A number

42
[Graphics:lecture3gr2.gif][Graphics:lecture3gr13.gif]

a string:

"hello"
[Graphics:lecture3gr2.gif][Graphics:lecture3gr14.gif]

a list of numbers:

{2.1, 2.2, 3.1}   (* In Mathematica *)
[Graphics:lecture3gr2.gif][Graphics:lecture3gr15.gif]

(2.1 2.2 3.1) (* In Scheme *)

an identifier (sometimes called Atom in Scheme, Symbol in Mathematica):

x
[Graphics:lecture3gr2.gif][Graphics:lecture3gr16.gif]

an expression:

2 + 3
[Graphics:lecture3gr2.gif][Graphics:lecture3gr17.gif]

Plus[2, 3]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr18.gif]

(+ 2 3) (* In Scheme *)

another expression:

(2+3) * (6/2)

Times[Plus[2, 3], Divide[6,2]]

(* (+ 2 3) (/ 6 2) ) (* In Scheme *) (* can be evaluated *)

(20 (apa 2 3) (/ 6 2) ) (* In Scheme *)
(* cannot be evaluated, since 20 is no function, and apa has no function value *)

List arguments to Scheme functions have to be quoted, in order to prevent illegal evaluation,
(append '(apa 2 3) '(30 6 2) ) (* OK!. Call append on two lists *)
Result: (apa 2 3 30 6 2)

Evaluation rules for Scheme and Mathematica

Constant symbols, numbers, and strings evaluate to themselves.

Identifiers are looked up in the current environment and are replaced by the values found there.

Expressions are evaluated by first evaluating the head/first expr, which should be a function. This function is then applied to the evaluated arguments.

func[arg1, arg2]    (* In Mathematica *)

(func arg1 arg2) (* In Scheme *)

Scheme has 1-step evaluation whereas Mathematica has infinite evaluation.

In the latter case, if the result of the first evaluation step is an expression that can still be evaluated, it is evaluated until it no longer changes value.

Conditional constructs

Make n into a constant

(if  (= a 0)        (* In Scheme *)
0
(/ 1 a))

a = 5.;              (* In Mathematica *)

If[ a==0,
0,
1/a
]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr19.gif]

(cond  ((= a 0)  0)      (* In Scheme *)
((= a 1) 1)
(T (/ 1 a))

a = 4.;              (* In Mathematica *)
b = bb;

Which[ a===0, 0,
b===1, 1,
True, 1/a
]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr20.gif]
Delayed evaluation and special forms

Both if, If, cond, Which are special forms which use delayed evaluation, i.e. do not evaluate all their arguments before the respective call.

Locally bound names

The let-expression in Scheme

(let  ((a 2)        (* In Scheme *)
(b 3))
(+ a b))

The With-expression (or Block or Module) in Mathematica

              (* In Mathematica *)

With[ {a = 2,
b = 3 },
a+b
]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr21.gif]

Higher-Order Functions (p 378)

Functions are first-class values, meaning that:

Functions can be passed as arguments

Functions can be returned as values

Such functions are called higher-order functions

MakeDouble is a higher-order function. It accepts a function as an argument and returns a doubling function.

MakeDouble[f_] := Module[{
doublefn
},
doublefn[x_] := f[x,x];
doublefn
]

Create a doubling function from Plus

double = MakeDouble[Plus]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr22.gif]

double[33]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr23.gif]

Create a doubling function from Times

sqr = MakeDouble[Times]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr24.gif]

sqr[9]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr25.gif]

ApplyToAll is a higher-order function that applies a function f to all elements in a list L.
It applies f to the first element of the list. The result is prepended to the rest of the list in converted form.

ApplyToAll[f_, L_] :=
If[ L=={},
{},
Prepend[ApplyToAll[f,Rest[L]], f[First[L]]]
]

(* (Cons x L) in Scheme is Prepend[L, x] in Mathematica *)

SqrLis applies sqr to all elements in the list L

SqrLis[L_] := ApplyToAll[sqr,L]

SqrLis[{aa,bb,3}]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr26.gif]


Join2 = MakeDouble[StringJoin];

StrJoinLis[L_] := ApplyToAll[Join2,L];

StrJoinLis[{"a","bb","bd"}]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr27.gif]

ApplyToAll is already built into Mathematica. It is called Map.

SqrLis2[L_] := Map[sqr,L]

SqrLis2[{aa,2,3}]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr28.gif]

Lambda expressions and anonymous functions (p 379)

A Lambda expression in Scheme gives a way of creating a function without giving it a name, i.e. an anonymous function. The word Lambda is borrowed from lambda calculus. A Lambda expression in Mathematica is created using Function[].

A lambda-expression in Scheme:

(lambda param-list body)


(lambda (x) (* x x))

A lambda-expression for a squaring function in Mathematica:

Function[{x}, x*x]

It can be assigned to a function variable sqr2

sqr2 = Function[{x}, x*x]
(* Note implicit multiplication syntax in x x below *)
[Graphics:lecture3gr2.gif][Graphics:lecture3gr29.gif]

and applied to a value

sqr2[4]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr30.gif]

A lambda expression can be used directly:

Function[{x}, x*x][5]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr31.gif]
Using lambda expressions to construct function values (p 380)

Construct a squaring function using a lambda expression

MakeDouble[f_] := Function[{x}, f[x,x]]

Use it:

sqr3 = MakeDouble[Times];

sqr3[5]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr32.gif]

Construct a general function composition function:

compose[g_,f_] := Function[{x}, g[f[x]]]

Use it:

func2 = compose[sqr3,sqr];

func2[2]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr33.gif]

Explicit composition of sqr3 and sqr gives the same result

sqr3[sqr[2]]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr34.gif]
Creating functions with persistent local variables (p 381)

MakeNewBalance creates an account with an initial balance, returning a withdrawing function.
The use of Module creates a local, lexically scoped variable balance that persists in the returned function. Such a returned function, with bound free variables, is often called a function closure.

MakeNewBalance[bal_] := Module[{balance = bal},
Function[{amount},
If[ balance<amount,
"Insufficient funds",
balance = balance-amount
]
]
]

Create two accounts and withdrawing functions

 withdraw1 = MakeNewBalance[100];

 withdraw2 = MakeNewBalance[100];

withdraw1
[Graphics:lecture3gr2.gif][Graphics:lecture3gr35.gif]

Withdraw a few times

withdraw1[20]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr36.gif]

withdraw2[50]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr37.gif]

withdraw1[20]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr38.gif]

withdraw2[60]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr39.gif]

Functional programming with static typing (p 381)

Strongly typed functional languages

Standard ML, Miranda, and Haskell are strongly typed functional languages.
These languages have a static type system that checks and infers types at compile-time.
Type parameters, type inference and polymorphism make these languages quite flexible

Weakly typed functional languages

Scheme and Mathematica are weakly typed functional languages.
Types are checked dynamically during execution.

Data structures in Miranda and Mathematica

Lists

Lists in Miranda

[1,2,3]

Lists in Mathematica

{1,2,3}
[Graphics:lecture3gr2.gif][Graphics:lecture3gr40.gif]
Data types with value constructors

Data types in Miranda. The items north, east, south and west are called data constructors.

datatype direction = north|east|south|west

Possible data type declaration in a dialect of Mathematica extended with type declarations

Datatype[direction = north|east|south|west]

Define a function heading using pattern matching on the four different data constructor values:

heading[north] := 0.0;
heading[east] := 90.0;
heading[south] := 180.0;
heading[west] := 270.0;

Call this function using the data arguments east and west, which matches the patterns east and west.

heading[east]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr41.gif]

heading[west]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr42.gif]
Pattern matching on lists (p 386)

The constructor {...} creates a list
The pattern identifier_ matches a single item
The pattern identifier___ matches zero or more items
The pattern {a_,x___} matches a list with one item, followed by zero or more items.
Use patterns to define the head and tail functions below:

head[{a_,x___}] := a;
tail[{a_,x___}] := {x};

Take the head of a list

head[{2,3,4}]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr43.gif]

Take the tail of a list

tail[{2,3,4}]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr44.gif]

Miranda/Haskell versus Mathematica (p390)

Miranda and Haskell use pattern matching to define functions.
Both languages use lazy evaluation (normal order evaluation) instead of eager (strict) evaluation as in Standard ML and for most Mathematica functions.

The factorial function in Miranda:

fact 0  = 1
fact n = n * fact(n-1)

The factorial function in Mathematica:

fact[0]  := 1;
fact[n_] := n * fact[n-1];

fact[5]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr45.gif]
Function definition using guards

Guards are boolean expressions that control the applicability of a definition

The factorial function using guards in Miranda:

fact n  = 1, if n=0
fact n = n * fact(n-1), if n>0

The factorial function using guards in Mathematica:

fact2[n_] :=  1    /; n==0;
fact2[n_] := n * fact[n-1] /; n>0;

fact2[6]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr46.gif]

Delayed evaluation (p 392)

An important problem in the design of funtional languages is the distinction between "ordinary functions" and special forms.
Special forms, e.g. If, do not evaluate their arguments in the standard way. C.f. short-circuit boolean evaluation.

For example, try to define your own If statement:

Myif[pred_,then_,else_] := If[pred,then,else]

If we use our own if, we get division by zero:

a3=0;
Myif[a3==0,55,10/a3]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr47.gif]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr48.gif]

But the predefined If works OK. Apparently it does not evaluate its second and third arguments until it has checked the predicate.

If[a3==0,55,10/a3]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr49.gif]
Delayed evaluation (normal order evaluation)

Delayed evaluation, also called normal order evaluation, means that arguments are substituted as far as possible before performing evaluation.
In Mathematica, delayed evaluation is possible by giving the function the property HoldAll, i.e. it avoids evaluation of arguments:

[Graphics:lecture3gr2.gif][Graphics:lecture3gr50.gif]

Both sq and sq2 uses normal-order evaluation.

[Graphics:lecture3gr2.gif][Graphics:lecture3gr51.gif]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr52.gif]

[Graphics:lecture3gr2.gif][Graphics:lecture3gr53.gif]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr54.gif]

Now set attributes to our own if function, Myif, so that only the first argument is evaluated.
In Mathematica, the attribute HoldRest makes the "rest" of the arguments avoid evaluation. Only the first argument, the predicate, will be evaluated.

SetAttributes[Myif, HoldRest];

Myif[a3==0,55,10/a3]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr55.gif]

ClearAttributes[Myif,HoldRest];   (* remove attributes *)
Delayed evaluation by thunks

Another way to achieve delayed evaluation, is to embed delayed expressions within functions, so called "thunks", which are passed as arguments.

DivByZero[] := 1/0;

MyIf2[x_,y_] := If[x, 1, y[]];

Calling MyIf2, choosing the True (then) part, works OK.

MyIf2[True,DivByZero]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr56.gif]

Choosing the False (else) part gives division by zero as it should:

MyIf2[False,DivByZero]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr57.gif]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr58.gif]
Lazy evaluation rules (p 396)

All arguments to user-defined functions are delayed

All bindings of local names in blocks are delayed

All arguments to constructor functions are delayed

All arguments to other predefined functions, such as +, -, *, etc. are evaluated

All function-valued arguments are evaluated

All conditions in selection function such as if and cond are forced

Currying (p 404)

A function in curried form (after H. B. Curry, the logician) allows higher-order functions to be created just by applying the function to a subset of its arguments.
For example, consider the function +:
+ is normally a function of two variables, as in Plus[2,3]
However, as a curried function one variable can be supplied at a time. Thus (+ 2) could be interpreted as a function that adds 2 to any value.

Approximately what we can do in a curried language:

 Addtwofn = (2 +)

That is not possible in Mathematica. We can use Function:

Addtwofn = Function[{x}, Plus[2,x]]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr59.gif]

Addtwofn[3]
[Graphics:lecture3gr2.gif][Graphics:lecture3gr60.gif]