Seed7

Parametri

La ricchezza concettuale di Seed7 la troviamo anche per quanto riguarda il comparto parametri legati alle funzioni. Inutile pedere tempo dicendo quanto questo aspetto sia importante in ogni linguaggio di programmazione e in questo caso direi che c'è veramente molto materiale, forse più che nella grande maggioranza di altri linguaggi.

Iniziamo subito presentando la keyword val che permette il passaggio di parametri per valore. Non vi sono quindi effetti di alcun genere sul parametro di partenza. In particolare con val viene copiato il valore stesso. L'esempio che segue illustra il concetto mettendolo in pratica.

  Esempio 5.1
1
2
3
4
5
6
7
8
9
10
11
$ include "seed7_05.s7i";

const proc: Fun (val integer: valParam) is func
begin
writeln(valParam);
end func;

const proc: main is func
begin
Fun(5);
end func;

Viene definita una funzione di nome "Fun" che accetta come parametro un valore di tipo intero (riga 3). Questo valore è usato a come una costante all'interno della funzione stessa (riga 5). Tale funzione è richiamata alla riga 10. Come si evince dall'output alla riga 18 il valore di x1 non è stato in alcun modo cambiato e non ptrebbe esserlo neanche voledo in quanto val ha accesso solo in lettura. Non è possibile con questo tipo di passaggio lavorare sul parametro, ovvero, inserendo per esempio dopo la riga 6 l'istruzione

varParam := 9;

Il compilatore si arrabbierebbe molto

*** s00010.sd7(6):52: Variable expected in {varParam := 9 } found parameter (in
integer: varParam)
varParam := 9;



La seconda keyword è ref. Le finalità sono all'incirca le medesime ma, a differenza di quanto avviene con val non viene effettuata una copia del valore ma viene istanziato un riferimento allo stesso. Questo può essere utile quando effettuare la copia è in qualche modo troppo oneroso in termine di risorse. In alcuni casi quindi le prestazioni potrebbero avere un qualche boost usando ref al posto di val. In pratica le cose cambiano poco come evidenzia l'esempio seguente in tutto simile al 5.1

  Esempio 5.2
1
2
3
4
5
6
7
8
9
10
11
$ include "seed7_05.s7i";

const proc: Fun (ref integer: refParam) is func
begin
writeln(refParam);
end func;

const proc: main is func
begin
Fun(5);
end func;

Anche ref ha accesso solo in lettura.
In realtà ci può essere un comportamento diverso, pur lavorando su uno stesso esempio, usando una volta ref ed un'altra val , il che evidenzia la differenza intima tra le due modalità di passaggio dei parametri. Questo avviene in casi estremi... comunque bisogna fare attenzione. Vediamo l'esempio tratto dal sito ufficiale:

  Esempio 5.3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ include "seed7_05.s7i";

var integer: aGlobal is 1;

const proc: aFunc (val integer: valParam, ref integer: refParam) is func
begin
writeln(valParam <& " " <& refParam);
aGlobal := 2;
writeln(valParam <& " " <& refParam);
end func;

const proc: main is func
begin
aFunc(aGlobal, aGlobal);
end func;

L'output di questo programma è il seguente:

1 1
1 2


Come si vede c'è stata una variazione nel secondo parametro. Questo perchè la variabile aGlobal  viene passata in valParam come valore e quindi il suo valore è in un certo senso blindato, mentre refParam riceve un puntatore e il valore, che è cambiato, viene in un certo senso ricaricato. Questo avviene perchè lavoriamo su di una variabile globale che può essere di qualsiasi tipo, non solo un intero. Fate quindi attenzione in questi casi.

Altro tipo di parametro è introdotto dalla parola in. Un parametro preceduto da detta keyword ha un comportamente un po' particolare in questo passa i parametri come valori o come riferimento dipendentemente dalla loro natura. Precisamente ci si basa sulla tabella che segue:

tipi passati come valore:
boolean, integer, float, char, category, reference, enumeration, clib_file, pixel, PRIMITIVE_SOCKET

tipi passati come ref:
bigInteger, rational, bigRational, complex, string, array, hash, set, bitset, struct, interface, ref_list, program, color, time, duration, file, text, proc, type, object

La differenza sta nelle dimensioni dei dati di rispettivi tipi: laddove sono piccole questi vengono passati per valore, dove sono grandi si ricorre al passaggio per riferimento. Per comprovarlo basta un semplice esempio, mutuato in parte dal 5.3

  Esempio 5.4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ include "seed7_05.s7i";


var integer: aGlobal is 0;

const proc: Fun (in integer: inParam) is func
begin
writeln(inParam);
aGlobal := 5;
writeln(inParam);
end func;

const proc: main is func
begin
Fun(aGlobal);
writeln(aGlobal);
end func;

  Esempio 5.5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ include "seed7_05.s7i";
include "bigint.s7i";

var bigInteger: aGlobal is 0_;

const proc: Fun (in bigInteger: inParam) is func
begin
writeln(inParam);
aGlobal := 5_;
writeln(inParam);
end func;

const proc: main is func
begin
Fun(aGlobal);
writeln(AGlobal);
end func;

L'esempio 5.4 restituisce come output

0
0
5

mentre il 5.5

0
5
5


la differenza consiste nelle due espressioni evidenziate. Nel 5.4 abbiamo in integer che, come da tabella, è trattato come parametro passato per valore, quindi l'istruzione alla riga 9 non ha alcun effetto sulla variabile globale definita alla riga 4.
Nel 5.5 tale variabile globale è un bigInteger e quindi esso è passato per riferimento quindi il cambiamento effettuato all'interno della funzione coinvolge anche il parametro originario.
Questo adattamento permette una gestione più efficiente ma, come visto è necessario sapere cosa si sta facendo visto che gli effetti potrebbero essere diversi a seconda del tipo. Anche in var non permette di lavorare sul parametro stesso, come in e ref.

Completamente per riferimento lavora invece inout, altro "canale" di passaggio per i nostri parametri:

  Esempio 5.6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ include "seed7_05.s7i";

var integer: aGlobal is 0;

const proc: Fun (inout integer: inoutParam) is func
begin
writeln(inoutParam);
inoutParam := 5;
writeln(inoutParam);
end func;

const proc: main is func
begin
Fun(aGlobal);
writeln(aGlobal);
end func;

L'output, come avrete intuito è

0
5
5

questo perchè anche a livello globale viene apportata la modifica alla riga 8 e aGlobal viene modificato definitivamente.

Infine abbiamo in var che lavora praticamente come val ma ci permette di apportare delle modifiche sul parametro al'interno del metodo, modifiche che comunque non impatteranno in alcun modo sui valori originali che rimarranno invariati. Ecco l'esempio:

  Esempio 5.7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ include "seed7_05.s7i";

var integer: aGlobal is 0;

const proc: Fun (in var integer: invarParam) is func
begin
writeln(invarParam);
invarParam := 5;
writeln(invarParam);
end func;

const proc: main is func
begin
Fun(aGlobal);
writeln(aGlobal);
end func;

Il risultato è:

0
5
0

L'istruzione alla riga stavolta è ammessa, contrariamente a quando visto per l'esempio 5.1. L'istruzione alla riga 8 ha effetto sulla screittura alla riga 9 mentre quella alla riga 15 restituisce il valore originale di aGlobal.

Complessivamente si può far riferimento alla seguente tabella:

Parametro Valutazione Accesso
val valore lettura sul parametro
ref riferimento lettura sul parametro
in riferimento - valore lettura sul parametro
inout riferimento scrittura sul parametro
in var valore scrittura sul parametro

Rispetto ad altri linguaggi come si vede c'è un po' di varietà in più; complessivamente, una volta imparate le facili differenze credo che vi troverete molto bene tenendo però ben presenti le peculiarità in particolare quando farete uso di in var.