Funzioni (F#)

Le funzioni sono l'unità fondamentale di esecuzione dei programmi in qualsiasi linguaggio di programmazione. Come per gli altri linguaggi, una funzione F# ha un nome, può disporre di parametri e accettare argomenti e ha un corpo. F# supporta inoltre costrutti di programmazione funzionale, ad esempio l'utilizzo di funzioni come valori, l'utilizzo di funzioni senza nome nelle espressioni, la composizione di funzioni per formare nuove funzioni, le funzioni sottoposte a currying e la definizione implicita di funzioni tramite applicazione parziale degli argomenti della funzione.

È possibile definire le funzioni utilizzando la parola chiave let oppure, se la funzione è ricorsiva, la combinazione di parole chiave let rec.

// Non-recursive function definition.
let [inline] function-name parameter-list [ : return-type ] = function-body
// Recursive function definition.
let rec function-name parameter-list = recursive-function-body

Note

L'elemento function-name è un identificatore che rappresenta la funzione. L'elemento parameter-list è costituito da parametri successivi separati da spazi. È possibile specificare un tipo esplicito per ogni parametro, come descritto nella sezione Parametri. Se non si definisce un tipo di argomento specifico, tramite il compilatore viene eseguito un tentativo di inferenza del tipo dal corpo della funzione. L'elemento function-body è costituito da un'espressione. L'espressione che costituisce il corpo della funzione è in genere un'espressione composta, costituita da diverse espressioni che culminano in un'espressione finale, che rappresenta il valore restituito. L'elemento return-type è costituito da due punti seguiti da un tipo ed è facoltativo. Se non si specifica in modo esplicito il tipo del valore restituito, il compilatore determina il tipo restituito dall'espressione finale.

Una definizione di funzione semplice è simile a quella riportata di seguito:

let f x = x + 1

Nell'esempio precedente il nome della funzione è f, l'argomento è x, di tipo int, il corpo della funzione è x + 1 e il valore restituito è di tipo int.

L'identificatore inline indica al compilatore che la funzione è di piccole dimensioni e che il codice per la funzione può essere integrato nel corpo del chiamante.

Ambito

A qualsiasi livello di ambito diverso dall'ambito del modulo, il riutilizzo di un valore o di un nome di funzione non costituisce un errore. Se si riutilizza un nome, il nome dichiarato successivamente nasconde quello dichiarato precedentemente. Nell'ambito di livello principale in un modulo, tuttavia, i nomi devono essere univoci. Il codice seguente comporta ad esempio la generazione di un errore se incluso nell'ambito del modulo, mentre non provoca errori se incluso in una funzione:

let list1 = [ 1; 2; 3]
// Error: duplicate definition. 
let list1 = []  
let function1 =
   let list1 = [1; 2; 3]
   let list1 = []
   list1

Il codice seguente è invece accettabile a qualsiasi livello di ambito:

let list1 = [ 1; 2; 3]
let sumPlus x =
// OK: inner list1 hides the outer list1. 
   let list1 = [1; 5; 10]  
   x + List.sum list1

Parametri

I nomi dei parametri sono elencati dopo il nome della funzione. È possibile specificare un tipo per un parametro, come illustrato nell'esempio seguente:

let f (x : int) = x + 1

Se si specifica un tipo, questo viene indicato dopo il nome del parametro ed è separato dal nome dai due punti. Se si omette il tipo per il parametro, questo viene derivato dal compilatore. Nella definizione di funzione seguente, ad esempio, l'argomento x viene considerato di tipo int perché 1 è di tipo int.

let f x = x + 1

Tramite il compilatore viene tuttavia eseguito un tentativo di rendere la funzione il più generica possibile. Notare, ad esempio, il codice seguente:

let f x = (x, x)

La funzione consente di creare una tupla da un argomento di qualsiasi tipo. Poiché il tipo non è specificato, la funzione può essere utilizzata con qualsiasi tipo di argomento. Per ulteriori informazioni, vedere Generalizzazione automatica (F#).

Corpi delle funzioni

Il corpo di una funzione può contenere definizioni di funzioni e variabili locali. Tali funzioni e variabili sono incluse nell'ambito nel corpo della funzione corrente ma non al di fuori di esso. Quando è abilitata l'opzione relativa alla sintassi leggera, è necessario utilizzare il rientro per indicare che una definizione è nel corpo di una funzione, come illustrato nell'esempio seguente:

let cylinderVolume radius length =
    // Define a local value pi. 
    let pi = 3.14159
    length * pi * radius * radius

Per ulteriori informazioni, vedere Linee guida per la formattazione del codice (F#) e Sintassi dettagliata (F#).

Valori restituiti

L'espressione finale nel corpo di una funzione viene utilizzata dal compilatore per determinare il tipo e il valore restituito. Il compilatore può derivare il tipo dell'espressione finale dalle espressioni precedenti. Nella funzione cylinderVolume, illustrata nella sezione precedente, il tipo di pi viene determinato in base al tipo del valore letterale 3.14159, che è float. Il compilatore utilizza il tipo di pi per determinare il tipo dell'espressione h * pi * r * r, che è float. Il tipo complessivo restituito della funzione è pertanto float.

Per specificare in modo esplicito il valore restituito, scrivere il codice come indicato di seguito:

let cylinderVolume radius length : float =
   // Define a local value pi. 
   let pi = 3.14159
   length * pi * radius * radius

Utilizzando il codice precedente, il compilatore applica float all'intera funzione. Se si desidera applicare il valore anche ai tipi di parametri, utilizzare il codice seguente:

let cylinderVolume (radius : float) (length : float) : float

Chiamata di una funzione

Per chiamare le funzioni è necessario specificare il nome della funzione seguito da uno spazio e quindi dagli argomenti desiderati, separati da spazi. Per chiamare, ad esempio, la funzione cylinderVolume e assegnare il risultato al valore vol, è necessario scrivere il codice seguente:

let vol = cylinderVolume 2.0 3.0

Applicazione parziale di argomenti

Se si fornisce un numero di argomenti minore rispetto a quello specificato, si crea una nuova funzione che richiede gli argomenti restanti. Questo metodo di gestione degli argomenti viene definito currying ed è una caratteristica dei linguaggi di programmazione funzionale, come F#. Si supponga, ad esempio, di utilizzare tubi di due dimensioni, uno con un raggio di 2.0 e l'altro con un raggio di 3.0. È possibile creare funzioni per determinare il volume dei tubi come indicato di seguito:

let smallPipeRadius = 2.0
let bigPipeRadius = 3.0

// These define functions that take the length as a remaining 
// argument: 

let smallPipeVolume = cylinderVolume smallPipeRadius
let bigPipeVolume = cylinderVolume bigPipeRadius

Viene quindi fornito l'argomento aggiuntivo in base a quanto necessario per le diverse lunghezze di tubi delle due diverse dimensioni:

let length1 = 30.0
let length2 = 40.0
let smallPipeVol1 = smallPipeVolume length1
let smallPipeVol2 = smallPipeVolume length2
let bigPipeVol1 = bigPipeVolume length1
let bigPipeVol2 = bigPipeVolume length2

Funzioni ricorsive

Le funzioni ricorsive sono funzioni che chiamano se stesse. Per queste funzioni è necessario specificare la parola chiave rec dopo la parola chiave let. La funzione ricorsiva deve essere richiamata dal corpo della funzione, come nel caso di qualsiasi chiamata di funzione. La funzione ricorsiva seguente consente di calcolare l'ennesimo numero di Fibonacci. La sequenza di numeri di Fibonacci è nota fin dall'antichità ed è una sequenza in cui ogni numero successivo è la somma dei due numeri precedenti nella sequenza.

let rec fib n = if n < 2 then 1 else fib (n - 1) + fib (n - 2)

Alcune funzioni ricorsive potrebbero comportare l'overflow dello stack del programma o non garantire prestazioni efficaci se non vengono scritte con cura e facendo attenzione all'utilizzo di tecniche speciali, come gli accumulatori e la continuazione.

Valori di funzione

In F# tutte le funzioni sono considerate valori e sono infatti note come valori di funzione. Poiché le funzioni sono valori, possono essere utilizzate come argomenti di altre funzioni o in altri contesti in cui vengono utilizzati i valori. Di seguito è illustrato un esempio di funzione che accetta un valore di funzione come argomento:

let apply1 (transform : int -> int ) y = transform y

È possibile specificare il tipo di un valore di funzione utilizzando il token ->. A sinistra del token è indicato il tipo dell'argomento e a destra il valore restituito. Nell'esempio precedente apply1 è una funzione che accetta una funzione transform come argomento, dove transform è una funzione che accetta un numero intero e restituisce un altro numero intero. Nel codice seguente viene illustrato come utilizzare apply1:

let increment x = x + 1

let result1 = apply1 increment 100

Dopo l'esecuzione del codice precedente, il valore di result sarà 101.

Più argomenti vengono separati da token -> successivi, come illustrato nell'esempio seguente:

let apply2 ( f: int -> int -> int) x y = f x y

let mul x y = x * y

let result2 = apply2 mul 10 20

Il risultato è 200.

Espressioni lambda

Un'espressione lambda è una funzione senza nome. Negli esempi precedenti, anziché definire le funzioni denominate increment e mul, è possibile utilizzare espressioni lambda come indicato di seguito:

let result3 = apply1 (fun x -> x + 1) 100

let result4 = apply2 (fun x y -> x * y ) 10 20

Per definire le espressioni lambda, è necessario utilizzare la parola chiave fun. Un'espressione lambda assomiglia a una definizione di funzione, ad eccezione del fatto che anziché il token =, viene utilizzato il token -> per separare l'elenco di argomenti dal corpo della funzione. Come in una definizione di funzione normale, i tipi di argomenti possono essere derivati o specificati in modo esplicito e il tipo restituito dell'espressione lambda viene derivato dal tipo dell'ultima espressione nel corpo. Per ulteriori informazioni, vedere Espressioni lambda: parola chiave fun (F#).

Composizione di funzioni e utilizzo di pipeline

Le funzioni in F# possono essere composte da altre funzioni. La composizione di due funzioni function1 e function2 è un'altra funzione che rappresenta l'applicazione di function1 seguita dall'applicazione di function2:

let function1 x = x + 1
let function2 x = x * 2
let h = function1 >> function2
let result5 = h 100

Il risultato è 202.

L'utilizzo di pipeline consente di concatenare chiamate di funzioni come operazioni successive. Il funzionamento è il seguente:

let result = 100 |> function1 |> function2

Il risultato è anche in questo caso 202.

Gli operatori di composizione accettano due funzioni e restituiscono una funzione, al contrario, gli operatori della pipeline utilizzano una funzione e un argomento e restituisce un valore. Nell'esempio di codice seguente viene illustrata la differenza tra la pipeline e gli operatori di composizione visualizzazione delle differenze nelle firme della funzione e utilizzare.

// Function composition and pipeline operators compared.

let addOne x = x + 1
let timesTwo x = 2 * x

// Composition operator
// ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3
let Compose2 = addOne >> timesTwo

// Backward composition operator
// ( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
let Compose1 = addOne << timesTwo

// Result is 5
let result1 = Compose1 2

// Result is 6
let result2 = Compose2 2

// Pipelining
// Pipeline operator
// ( <| ) : ('T -> 'U) -> 'T -> 'U
let Pipeline1 x = addOne <| timesTwo x

// Backward pipeline operator
// ( |> ) : 'T1 -> ('T1 -> 'U) -> 'U
let Pipeline2 x = addOne x |> timesTwo

// Result is 5
let result3 = Pipeline1 2

// Result is 6
let result4 = Pipeline2 2

Funzioni in overload

È possibile eseguire l'overload dei metodi di tipo ma non di funzioni. Per ulteriori informazioni, vedere Metodi (F#).

Vedere anche

Altre risorse

Valori (F#)

Riferimenti per il linguaggio F#