by

Peter Fritzson,

PELAB - Programming Environment Laboratory

Dept. of Computer and Information Science,

Linköping University, Sweden

(chapter 7, p 213-233)

Arguments are evaluated before passing those to the function

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

Myplus[2*3,10]

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

Foo3[2*3,10]

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]

a=p

temp=p; t[[p]]=

Try to use it

p=1; q = 2;

swap[p,q];

{p,q}

Working case

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

swap[t[[p]], p];

{p,t}

Problematic case

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

swap[p, t[[p]]];

{t,p}

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

swap2[p, t[[p]]]

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

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

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.

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.

(Chapter 10, p 358)

Off[General::"spell1"]

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")

Pure functional programming

No side effects

Referential transparency

A construct has the same meaning wherever it legally occurs

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]

Conventional function style (p 361)

gcd[u_,v_] := Block[{

},

If[v==0,

Return[u]

,

Return[gcd[v,Mod[u,v]]]

]

];

gcd[8,18]

Shorter version (Scheme-like)

gcd[u_,v_] :=

If[v==0,

u

,

gcd[v, Mod[u,v]]

];

gcd[8,16]

Rule version (p 361)

(recurrence equations)

gcd[u_,0] := u;

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

gcd[8,28]

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

A number

42

a string:

"hello"

a list of numbers:

{2.1, 2.2, 3.1} (* InMathematica*)

(2.1 2.2 3.1) (* In Scheme *)

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

x

an expression:

2 + 3

Plus[2, 3]

(+ 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)

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] (* InMathematica*)

(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.

Make n into a constant

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

0

(/ 1 a))

a = 5.; (* InMathematica*)

If[ a==0,

0,

1/a

]

(cond ((= a 0) 0) (* In Scheme *)

((= a 1) 1)

(T (/ 1 a))

a = 4.; (* InMathematica*)

b = bb;

Which[ a===0, 0,

b===1, 1,

True, 1/a

]

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

The let-expression in Scheme

(let ((a 2) (* In Scheme *)

(b 3))

(+ a b))

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

(* InMathematica*)

With[ {a = 2,

b = 3 },

a+b

]

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]

double[33]

Create a doubling function from Times

sqr = MakeDouble[Times]

sqr[9]

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] inMathematica*)

SqrLis applies sqr to all elements in the list L

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

SqrLis[{aa,bb,3}]

Join2 = MakeDouble[StringJoin];

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

StrJoinLis[{"a","bb","bd"}]

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

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

SqrLis2[{aa,2,3}]

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 *)

and applied to a value

sqr2[4]

A lambda expression can be used directly:

Function[{x}, x*x][5]

Construct a squaring function using a lambda expression

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

Use it:

sqr3 = MakeDouble[Times];

sqr3[5]

Construct a general function composition function:

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

Use it:

func2 = compose[sqr3,sqr];

func2[2]

Explicit composition of sqr3 and sqr gives the same result

sqr3[sqr[2]]

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

Withdraw a few times

withdraw1[20]

withdraw2[50]

withdraw1[20]

withdraw2[60]

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

Scheme and *Mathematica* are weakly typed functional languages.

Types are checked dynamically during execution.

Lists in Miranda

[1,2,3]

Lists in Mathematica

{1,2,3}

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]

heading[west]

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}]

Take the tail of a list

tail[{2,3,4}]

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]

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]

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]

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]

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:

Both sq and sq2 uses normal-order evaluation.

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]

ClearAttributes[Myif,HoldRest]; (* remove attributes *)

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]

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

MyIf2[False,DivByZero]

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

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]]

Addtwofn[3]