Procedures

Definitie van procedures

Als men meerdere statements na elkaar wil uitvoeren en gebruik maken van lokale variabelen voor het schrijven van veiliger programma's maakt men gebruik van procedures.

> Pyth := proc(x, y)
   local r;

   r := x^2 + y^2;

   sqrt(r)

end;

Pyth := proc (x, y) local r; r := x^2+y^2; sqrt(r) end proc

Een procedure in Maple kan een willekeurig aantal Maple statements bevatten, inclusief definities van nieuwe (lokale) procedures of functies. Procedures in Maple gedragen zich eigenlijk als functies, ze geven een waarde terug als ze aangeroepen worden. Deze waarde is die van het laatste statement dat uitgevoerd wordt. Het onderscheid tussen procedures en functies situeert zich vooral op het vlak van het ontwikkelen van libraries. Procedures lenen zich beter om robuste code te schrijven, zie hiervoor Types voor procedure parameters en Uitvoeringsfouten en 'Error'.

> Pyth(6, 7);

85^(1/2)

De procedure 'Pyth' heeft twee parameters 'x' en 'y'. Het eerste statement 'local r' declareert de locale variabele 'r'. In het tweede statement wordt een berekening met 'x' en 'y' uitgevoerd, het resultaat van het laatste statement is de waarde die de procedure teruggeeft. Aangezien zowel 'x', 'y' als 'r' lokaal gedefinieerd zijn is aan deze variabelen nog steeds geen waarde toegekend op het niveau van het worksheet.

> x, y, r;

x, y, r

Indien men nu echter het 'local r' statement zou weglaten dan is Maple intelligent genoeg om zelf 'r' lokaal te maken.

> Pyth2 := proc(x, y)
   r := x^2 + y^2;

   sqrt(r)

end:

Warning, `r` is implicitly declared local to procedure `Pyth2`

Het is echter in ieder geval te verkiezen zelf de 'local' declaratie te doen. Voor een grondiger discussie van dit onderwerp, zie de sectie Lokale vs. globale parameters.

Maple beschikt over het 'return' statement. Uitvoeren ervan heeft als gevolg dat de uitvoering van de procedure die het bevat onderbroken wordt en de waarde van de expressie die het argument van 'a' bevat als resultaat van de procedure teruggegeven wordt.

> Fact := proc(n)
   if(n < 2) then

       return 1;

   end if;

   return mul(i, i=2..n)

end:

> Fact(0), Fact(1), Fact(2), Fact(3);

1, 1, 2, 6

Hoewel het tweede 'return' statement niet strikt noodzakeelijk is verdient het toch aanbeveling steeds 'return' te gebruiken omdat het de bedoelde semantiek beklemtoont.

> Pyth := 'Pyth': Pyth2 := 'Pyth2': Fact := 'Fact':

Recursie

Procedures kunnen zichzelf recursief oproepen, een voorbeeld hiervan wordt hieronder gegeven:

> Fact := proc(n)
   if(n < 2) then

       return 1;

   end if;

   return n*Fact(n - 1)

end:

> Fact(5);

120

Het 'if' statement zorgt ervoor dat de recursie stopt.

> Fact := 'Fact':

Types voor procedure parameters

Vanuit het standpunt van de gebruiker is het prettig als het ingeven van parameters met een verkeerd type niet leidt tot onzinnige resultaten, maar resulteren in een foutmelding. Voor korte Maple sessies is dit niet van groot belang, maar bij een meer gesofisticeerd gebruik of bij programmeren van libraries is het essentieel.

> Macht1 := proc(x, n)
   local Prod, i;

   Prod := 1;

   for i from 1 to n do

       Prod := Prod*x;

   end do;

   return Prod;

end:

> Macht1(2.5, 3.1);

15.625

Dit resultaat is niet precies wat men zou verwachten:

> 2.5^3.1;

17.12434729

Dit eenvoudige voorbeeld toont aan dat het aangeven van het verwachte type voor parameters geen overbodige luxe is. De volgende versie van de procedure waarschuwt de gebruiker dat er andere dingen van de procedure verlangd worden dan voorzien door de programmeur (dikwijls een en dezelfde persoon).

> Macht2 := proc(x, n::posint)
   local Prod, i;

   Prod := 1;

   for i from 1 to n do

       Prod := Prod*x;

   end do;

   return Prod;

end:

> Macht2(-2.3,2);

5.29

> Macht2(2.5, 3.1);

Error, invalid input: Macht2 expects its 2nd argument, n, to be of type posint, but received 3.1

Er zijn vrij veel datatypes in Maple, enkele van de meest gebruikte zijn

integer

nonneg: natuurlijk getal

posint: positief natuurlijk getal

realcons: constant reeel getal

name: naam van een Maple variabele

Ter illustratie van het 'name' type, het volgende voorbeeld:

> Gaussian1 := proc(Expr, X, A)
   return evalf(Int(Expr*exp(-X^2/2),X=-A..A)/sqrt(2*Pi));

end:

> Gaussian1(x^2, x, 1.0);

.1987480430

> x := a + b;

x := a+b

> Gaussian1(x^2, x, 1.0);

Error, (in evalf/int) invalid arguments

Deze foutmelding is tamelijk cryptisch, het specificeren van types voor de parameters verhelpt dit.

> Gaussian2 := proc(Expr::algebraic, X::name, A::realcons)
   return evalf(Int(Expr*exp(-X^2/2),X=-A..A)/sqrt(2*Pi));

end:

> Gaussian2(x^2, x, 1.0);

Error, invalid input: Gaussian2 expects its 2nd argument, X, to be of type name, but received a+b

> Gaussian2('x'^2, 'x', 1.0);

.1987480430

> Macht1 := 'Macht1': Macht2 := 'Macht2': x := 'x':
Gaussian1 := 'Gaussian1': Gaussian2 := 'Gaussian2':

Functies als functie parameters

Het is duidelijk uit een groot aantal voorbeelden dat Maple procedures als parameters procedures kunnen hebben.  Denk hierbij aan de functies 'diff', 'int' en vele andere.  Het is toch nuttig hier even speciaal de aandacht op te vestigen aangezien een groot aantal programmeertalen waaronder Pascal en Java dit niet toelaten.  In Java is deze beperking te omzeilen zoals geillustreerd in het Collections framework.

Een voorbeeld van een hogere orde procedure, dit wil zeggen, een procedure die een procedure als parameter heeft:

> gaussInt := proc(f::procedure)
 local x;

 return int(f(x)*exp(-x^2/2)/sqrt(2*Pi),x);

end:

> gaussInt(x -> x^2);

1/2*2^(1/2)*(-x*exp(-1/2*x^2)+1/2*Pi^(1/2)*2^(1/2)*erf(1/2*2^(1/2)*x))/Pi^(1/2)

> gaussInt(`sin`);

int(1/2*sin(x)*exp(-1/2*x^2)*2^(1/2)/Pi^(1/2), x)

> gaussInt := 'gaussInt':

Functies als resultaat van functies

Functies kunnen als parameters meegegeven worden aan functies, maar functies kunnen ook teruggegeven worden door functies.  Beschouw het volgende eenvoudige voorbeeldje:

> powerBuilder := n -> (x -> x^n);

powerBuilder := proc (n) options operator, arrow; proc (x) options operator, arrow; x^n end proc end proc

De functie 'powerBuilder' beeldt 'n' af op de (anonyme) functie 'x -> x^n'.  'powerBuilder' "maakt" dus functies:

> square := powerBuilder(2);

square := proc (x) options operator, arrow; x^2 end proc

> cube := powerBuilder(3);

cube := proc (x) options operator, arrow; x^3 end proc

> inverse := powerBuilder(-1);

inverse := proc (x) options operator, arrow; x^(-1) end proc

> square(2); cube(2); inverse(2);

4

8

1/2

Lokale vs. globale variabelen

Variabelen gedefinieerd in een Maple worksheet zijn steeds globaal, ie. hun waarde kan gebruikt en gewijzigd worden in elke procedure of functie die gedefinieerd wordt. Hetzelfde geldt voor de procedures en functies zelf, ook deze kunnen door elke andere procedure of functie opgeroepen worden.

> BA := 5;

A := 5

> f1 := proc(x)
   A*x

end:

> f1(2);

10

Indien men nu de waarde van de globale variabele verandert, dan zal een nieuwe functie-aanroep een ander resultaat opleveren:

> A := 3;

A := 3

> f1(2);

6

> A := 'A': f1 := 'f1':

Als een procedure lokaal wordt gedefinieerd ten opzichte van een andere, dan is het gedrag van Maple analoog aan dat van een programmeertaal zoals Pascal.

> A := 5;

A := 5

> f2 := proc(x)
 local A, f1;

 A := 3;

 f1 := proc(y)

     A*y

 end;

 return f1(x/2);

end:

> f2(2);

3

De waarde van de globale variabele A is uiteraard niet gewijzigd.

> A;

5

> A := 'A': f2 := 'f2':

In de procedure f1 wordt de waarde van de lokale variabele A gebruikt, niet de globale waarde 5.

Het volgende geval lijkt analoog, maar is het duidelijk niet.

> A := 5;

A := 5

> f1 := proc(x)
   return A*x

end:

> f1(2);

10

In een procedure die gebruik maakt van f1 wordt eveneens de waarde van de globale parameter A gebruikt

> f2 := proc(x)
   return f1(x/2);

end:

> f2(2);

5

Als A als lokale variabele in de procedure f2 gedeclareerd wordt dan zou men verwachten dat de waarde van de lokale variabele in f1 gebruikt wordt.

> f3 := proc(x)
   local A;

   A := 4;

   return f1(x/2)

end:

> f3(2);

5

Dit blijkt niet het geval te zijn, dit is echter normaal. De variabele A die gebruikt werd in de definitie van f1 was immers een globale variabele. De locale variabele A gedeclareerd in f3 is een andere variabele voor de procedure f1, de A in de procedure f1 verwijst nog steeds naar de globale variabele A.

> A := 'A': g1 := 'g1': g2 := 'g2': g3 := 'g3':

Illustratie: sommen en producten

Het bereken van sommen en producten in Maple kan eenvoudig via de functies 'sum' en 'product'. Aangezien beiden dezelfde syntax hebben geldt de onderstaande bespreking van 'sum' ook voor 'product'.

> sum(x^'k'/'k'!, 'k'=0..10);

1+x+1/2*x^2+1/6*x^3+1/24*x^4+1/120*x^5+1/720*x^6+1/5040*x^7+1/40320*x^8+1/362880*x^9+1/3628800*x^10

De syntax is zoals men die zou verwachten, behalve misschien de aanwezigheid van de single quotes omheen 'k'. Essentieel zorgen deze ervoor dat 'k' een lokale variabele is in de som zodat ongewenste neveneffecten vermeden worden. Dit wodt door onderstaand voorbeeld geillustreerd.

> k := 3;

k := 3

> sum(x^k/k!, k=0..10);

Error, (in sum) summation variable previously assigned, second argument evaluates to 3 = 0 .. 10

> sum(x^'k'/'k'!, 'k'=0..10);

1/6+1/6*x+1/6*x^2+1/6*x^3+1/6*x^4+1/6*x^5+1/6*x^6+1/6*x^7+1/6*x^8+1/6*x^9+1/6*x^10

> k := 'k':

Dit voorbeeld illustreert duidelijk dat men best de nodige aandacht kan schenken aan de scope van een variabele. Het illustreert tevens een belangrijke maxim: "Werk steeds zoveel mogelijk met lokale variabelen".

In Maple beschikt men echter over een alternatief dat dit scoping probleem omzeilt: het gebruik van de functies 'add' en 'mul'.

> k := 3;

k := 3

> add(x^k/k!, k =0..10);

1+x+1/2*x^2+1/6*x^3+1/24*x^4+1/120*x^5+1/720*x^6+1/5040*x^7+1/40320*x^8+1/362880*x^9+1/3628800*x^10

> k := 'k':

Er is echter een beperking van de 'add' en 'mul' functies tov. 'sum' en 'product'. Deze laatsten kunnen gebruikt worden voor symbolische sommen en producten, hetgeen met 'add' en 'mul' niet het geval is.

> sum(('k')^2, 'k'=1..n);

1/3*(n+1)^3-1/2*(n+1)^2+1/6*n+1/6

> add(k^2, k=1..n);

Error, unable to execute add

Voor expliciete sommen en producten verkleint men de kans op moeilijkheden aanzienlijk door het gebruik van 'add' en 'mul'.

Uitvoeringsfouten en 'error'

Het is voor de gebruiker (en voor uzelf) best zo accuraat mogelijke informatie te krijgen wanneer er iets misloopt in een procedure. Hiervoor kan de functie 'error' gebruikt worden. Het uitvoeren ervan heeft effect dat de gebruiker een boodschap krijgt en de uitvoering van de procedure stopt. Dit wordt geillustreerd voor een eenvoudige implementatie van de Newton-Raphson methode voor het zoeken van nulpunten. De methode is iteratief en de convergentie is niet a-priori zeker, zodat het veiliger is een maximum aantal iteraties 'maxiter' te specificeren. Wordt dit aantal overschreden, dan stopt de procedure met een foutmelding.

> NewtonRaphson := proc(f::algebraic ,x0::realcons, s::realcons)
   local xx0, m, n, d, maxiter;

   n := 0;

   maxiter := 30;

   m := x0;

   xx0 := m - f(m)/D(f)(m);

   while (abs(m - xx0) > s) do

       m := xx0;

       xx0 := m - f(m)/D(f)(m);

       n := n + 1;

       if (n > maxiter) then

           error "meer dan %1 iteraties", maxiter;

       end if;

   end do;

   return m, n;

end:

> f := x -> exp(-x) - 1e-9;

f := proc (x) options operator, arrow; exp(-x)-0.1e-8 end proc

> NewtonRaphson(f, 20.0, 1e-10);

20.72326584, 5

> NewtonRaphson(f, -10.0, 1e-9);

Error, (in NewtonRaphson) meer dan 30 iteraties

> NewtonRaphson := 'NewtonRaphson': f := 'f':