Una funzione è un codice riutilizzabile che fornisce istruzioni per l'esecuzione di un'azione, come ad esempio Dance()
o Sleep()
, e produce output diversi in base all'input fornito.
Le funzioni forniscono astrazione per i comportamenti, ovvero, essendo riutilizzabili, nascondono i dettagli di implementazione non rilevanti per altre parti del codice e che non è necessario visualizzare.
Utilizziamo l'ordinazione di cibo da un menu come esempio per comprendere il concetto di funzioni e astrazione. La funzione per ordinare cibo potrebbe assomigliare a quanto segue:
OrderFood(MenuItem : string) : food = {...}
Quando ordini cibo in un ristorante, dici al cameriere quale piatto del menu desideri, OrderFood("Ramen")
. Non sai come il ristorante preparerà il piatto, ma ti aspetti di ricevere qualcosa che è considerato un food
dopo l'ordine. Altri clienti possono ordinare piatti diversi dal menu e anche aspettarsi di ricevere il loro cibo.
Per questo motivo le funzioni sono utili: è sufficiente definire queste istruzioni in un unico posto, in questo caso, definire cosa dovrebbe accadere quando qualcuno ordina cibo. È quindi possibile riutilizzare la funzione in contesti diversi, ad esempio per ogni cliente del ristorante che ordina dal menu.
Le sezioni seguenti descrivono come creare una funzione e come utilizzare una funzione una volta definita.
Definire le funzioni
La firma della funzione dichiara il nome della funzione (identificatore), l'input (parametri) e l'output (risultato) della funzione.
Le funzioni Verse possono anche avere specificatori che specificano come utilizzare o implementare una funzione.
La funzione corpo è un blocco di codice che definisce cosa fa la funzione quando viene chiamata.
Le sezioni seguenti spiegano questi concetti in modo più dettagliato.
La funzione sintassi è simile alla seguente:
Identifier(parameter1 : type, parameter2 : type) <specifier> : type = {}

Parametri
Un parametro è una variabile di input dichiarata in una firma della funzione e viene utilizzato nel corpo della funzione. Quando si chiama una funzione, è necessario assegnare valori ai parametri, se presenti. Questi valori assegnati sono chiamati argomenti della funzione.
Una funzione non può avere parametri, ad esempio Sleep()
, o tutti i parametri necessari. È possibile dichiarare un parametro nella firma della funzione specificando un identificatore e il tipo tra parentesi ()
. Se si dispone di più parametri, questi devono essere separati da virgole ,
.
Ad esempio:
Example(Parameter1 : int, Parameter2 : string) : string = {}
Quanto segue è tutto valido:
Foo():void = {}
Bar(X:int):int = X
Baz(X:int, ?Y:int, ?Z:int = 0) = X + Y + Z
La sintassi ?Y:int
definisce un argomento denominato con il nome Y
di tipo int
.
La sintassi ?Z:int = 0
definisce un argomento denominato Z
di tipo int
che non deve essere fornito quando viene chiamata la funzione, ma utilizza 0
come valore se non viene fornito.
Risultato
Il risultato è l'output di una funzione quando tale funzione viene chiamata. Il tipo restituito specifica il tipo di valore che ci si può aspettare dalla funzione se viene eseguita correttamente.
Se non si desidera che la funzione abbia un risultato, è possibile impostare il tipo restituito su void
. Le funzioni con void
come tipo restituito restituiscono sempre il valore false
, anche quando si specifica un'espressione di risultato nel corpo della funzione.
Specificatori
Oltre agli specificatori sulla funzione che descrivono il comportamento della funzione definita, possono essere presenti specificatori sull'identificatore (il nome) della funzione. Ad esempio:
Foo<public>(X:int)<decides>:int = X > 0
In questo esempio viene definita una funzione denominata Foo
accessibile pubblicamente e con effetto decides
. Gli specificatori dopo l'elenco dei parametri e prima del tipo restituito descrivono semantiche della funzione e contribuiscono al tipo della funzione risultante. Gli specificatori sul nome della funzione indicano solo il comportamento correlato al nome della funzione definita, come ad esempio la sua visibilità.
Corpo funzione
Il corpo funzione è il blocco di codice che definisce cosa fa la funzione. La funzione utilizza tutti i parametri definiti nella firma della funzione del corpo della funzione per creare un risultato.
Una funzione restituisce automaticamente il valore prodotto dall'ultima espressione eseguita.
Ad esempio:
Foo()<decides>:void = {}
Bar():int =
if (Foo[]):
1
else:
2
La funzione Bar()
restituisce o 1
o 2
, a seconda che Foo[]
abbia esito negativo.
Per forzare la restituzione di un determinato valore (e l'uscita immediata della funzione), utilizza l'espressione return
.
Ad esempio:
Find(X:[]int, F(:int)<decides>:void)<decides>:int =
for (Y:X, F(Y)):
return Y
false?
L'espressione return Y
esce dalla funzione Find
, restituendo il valore contenuto in Y
al chiamante della funzione. Tieni presente che false?
viene utilizzato per forzare la funzione ad avere esito negativo. In questo caso, questo è sensato, poiché nessun valore corrispondente al predicato F
è stato trovato in X
. Se viene definita una funzione come funzione che restituisce void
, non è necessario fornire un'espressione con l'espressione return
.
Ad esempio:
AnyOf(X:[]int, F(:int)<decides>:void)<decides>:void =
for (Y:X, F(Y)):
return
false?
Effetti
Gli effetti su una funzione descrivono comportamenti aggiuntivi che possono essere assunti dalla funzione quando viene chiamata. In particolare, l'effetto decides
su una funzione indica che la funzione potrebbe avere esito negativo in un modo che il chiamante potrebbe gestire (o propagarsi al suo chiamante essendo anche contrassegnato come decides
).
Ad esempio:
Fail()<decides>:void = false?
Questo definisce una funzione che ha sempre esito negativo. Qualsiasi chiamante dovrebbe gestire o propagare l'errore. Nota la sintassi: l'effetto è descritto come uno specificatore sulla funzione. Il tipo di una funzione con tale effetto può essere reso molto simile alla definizione della funzione tramite la macro di tipo:
type{_()<decides>void}
Chiamata di funzioni
Una chiamata di funzione è una espressione che valuta (chiama o richiama) una funzione.
Ci sono due forme per le chiamate di funzione in Verse:
-
FunctionName(Arguments)
: Questa forma richiede che la chiamata di funzione abbia esito positivo e possa essere utilizzata in qualsiasi contesto. -
FunctionName[Arguments]
: Questa forma indica che la chiamata di funzione può avere esito negativo. Per utilizzare questa forma, la funzione deve essere definita con lo specificatore<decides>
e chiamata in un contesto di fallimento.
Il richiamo viene eseguito utilizzando le parentesi se la funzione non ha l'effetto decides
. Ad esempio:
Foo()
Bar(1)
Baz(1, ?Y := 2)
Baz(3, ?Y := 4, ?Z := 5)
Baz(6, ?Z := 7, ?Y := 8)
Tieni presente come gli argomenti denominati, ad esempio ?Y:int
, vengono passati facendo riferimento al nome anteposto da ?
e fornendo un valore a destra di :=
. Tieni anche presente che l'argomento ?Z
è opzionale. È importante sottolineare che l'ordine degli argomenti denominati nel sito della chiamata è irrilevante, ad eccezione di eventuali effetti collaterali che possono verificarsi durante la produzione del valore per l'argomento denominato.
Per richiamare una funzione che ha l'effetto decides
, è necessario utilizzare le parentesi quadre. Ciò consente all'indicizzazione dell'array, che comporta l'effetto decides
, di seguire una sintassi simile alle funzioni contrassegnate con l'effetto decides
. Ad esempio:
Foo()<decides>:void = {}
Bar():int =
if (Foo[]):
1
else:
2
Decompressione della tupla
Una funzione che accetta più argomenti è indistinguibile quando viene richiamata da una funzione che accetta un singolo argomento tupla con elementi dello stesso tipo degli argomenti multipli. La mancanza di distinzione quando viene richiamata si applica anche al tipo di ciascuna funzione: hanno lo stesso tipo.
Ad esempio:
Second(:any, X:t where t:type):t = X
Ciò equivale a:
Second(X:tuple(any, t) where t:type):t = X(1)
Entrambi possono essere richiamati come:
X := 1
Y := 2
Second(X, Y)
or
X:tuple(int, int) = (1, 2)
Second(X)
Entrambi soddisfano il tipo type{_(:any, :t where t:type):t}
.
Il tipo di funzione
Il tipo di una funzione è costituito dal tipo del suo parametro (potenzialmente definito come tupla decompressa), dal suo effetto e dal tipo del suo risultato. Ad esempio:
type{_(:type1, :type2)<effect1>:type3}
Questo è il tipo di una funzione che accetta due argomenti di type1
e type2
(o equivalentemente, un argomento di tipo tuple(type1, type2)
), produce l'effetto effect1
e restituisce un valore di tipo type3
.
Sovraccarico
Più funzioni possono condividere lo stesso nome purché non vi siano argomenti che soddisfino più di una di tali funzioni. Questo è noto come sovraccarico.
Ad esempio:
Next(X:int):int = X + 1
Next(X:float):float = X + 1
int_list := class:
Head:int
Tail:?int_list = false
Next(X:int_list)<decides>:int_list = X.Tail?
Non vi è alcuna sovrapposizione tra gli argomenti accettati da queste funzioni. La funzione corretta da richiamare può essere risolta in modo inequivocabile dai tipi forniti. Ad esempio:
Next(0)
Next(0.0)
Next(int_list{Head := 0, Tail := int_list{Head := 1}})
Tuttavia, non è consentito quanto segue:
First(X:int, :any):int = X
First(X:[]int)<decides>int = X[0]
Questo perché tupla e array hanno una relazione di sottotipizzazione: array è un supertipo di tupla quando il tipo base dell'array è un supertipo di tutti i tipi di elemento della tupla. Ad esempio:
X := (1, 2)
First(X)
In questo esempio, la chiamata a First
può essere soddisfatta da entrambe le definizioni di First
. Nel caso di classi e interfacce non può verificarsi alcun sovraccarico poiché una classe può essere modificata successivamente per implementare un'interfaccia o due classi possono essere modificate per avere una relazione di ereditarietà. È invece necessario utilizzare l'override del metodo. Ad esempio,
as_int := interface:
AsInt():int
ToInt(X:as_int):int = X.AsInt()
thing1 := class(as_int):
AsInt():int = 1
thing2 := class(as_int):
AsInt():int = 2
Main()<decides>:void =
X := thing1{}
ToInt(X) = 1
Y := thing2{}
ToInt(Y) = 2
Una funzione che fa parte di una definizione di classe è chiamata metodo e dispone di funzionalità aggiuntive. Fai riferimento a Classe per saperne di più sui metodi dopo aver acquisito familiarità con le funzioni in Verse.