Condividi tramite


Funzioni

Le funzioni sono l'unità fondamentale dell'esecuzione del programma in qualsiasi linguaggio di programmazione. Come in altri linguaggi, una funzione F# ha un nome, può avere parametri e accettare argomenti e ha un corpo. F# supporta anche costrutti di programmazione funzionale, ad esempio la gestione di funzioni come valori, l'uso di funzioni senza nome nelle espressioni, la composizione di funzioni per formare nuove funzioni, funzioni curried e la definizione implicita delle funzioni tramite l'applicazione parziale degli argomenti di funzione.

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

Sintassi

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

Osservazioni:

Il nome della funzione è un identificatore che rappresenta la funzione. L'elenco di parametri è costituito da parametri successivi separati da spazi. È possibile specificare un tipo esplicito per ogni parametro, come descritto nella sezione Parametri. Se non si specifica un tipo di argomento specifico, il compilatore tenta di dedurre il tipo dal corpo della funzione. Il corpo della funzione è costituito da un'espressione. L'espressione che costituisce il corpo della funzione è in genere un'espressione composta costituita da una serie di espressioni che culminano in un'espressione finale che è il valore restituito. Il tipo restituito è un punto e due punti seguito da un tipo ed è facoltativo. Se non si specifica il tipo del valore restituito in modo esplicito, il compilatore determina il tipo restituito dall'espressione finale.

Una definizione di funzione semplice è simile alla seguente:

let f x = x + 1

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

Le funzioni possono essere contrassegnate come inline. Per informazioni su inline, vedere Funzioni inline.

Ambito

A qualsiasi livello di ambito diverso dall'ambito del modulo, non è un errore riutilizzare un valore o un nome di funzione. Se si riutilizza un nome, il nome dichiarato in un secondo momento shadowsrà il nome dichiarato in precedenza. Tuttavia, nell'ambito principale di un modulo i nomi devono essere univoci. Ad esempio, il codice seguente genera un errore quando viene visualizzato nell'ambito del modulo, ma non quando viene visualizzato all'interno di una funzione:

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

let function1 () =
    let list1 = [ 1; 2; 3 ]
    let list1 = []
    list1

Tuttavia, il codice seguente è 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, segue il nome del parametro e viene separato dal nome da due punti. Se si omette il tipo per il parametro , il tipo di parametro viene dedotto dal compilatore. Nella definizione di funzione seguente, ad esempio, l'argomento x viene dedotto come di tipo perché 1 è di tipo intint.

let f x = x + 1

Tuttavia, il compilatore tenterà di rendere la funzione il più generico possibile. Si noti ad esempio il codice seguente:

let f x = (x, x)

La funzione crea una tupla da un argomento di qualsiasi tipo. Poiché il tipo non è specificato, la funzione può essere usata con qualsiasi tipo di argomento. Per altre informazioni, vedere Generalizzazione automatica.

Corpi di funzione

Un corpo della funzione può contenere definizioni di variabili e funzioni locali. Tali variabili e funzioni si trovano nell'ambito nel corpo della funzione corrente, ma non al di fuori di esso. È necessario usare il rientro per indicare che una definizione si trova in un corpo della funzione, come illustrato nell'esempio seguente:

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

Per altre informazioni, vedere Linee guida per la formattazione del codice e sintassi dettagliata.

Valori restituiti

Il compilatore usa l'espressione finale in un corpo della funzione per determinare il valore restituito e il tipo. Il compilatore potrebbe dedurre il tipo dell'espressione finale dalle espressioni precedenti. Nella funzione cylinderVolume, illustrata nella sezione precedente, il tipo di pi viene determinato dal tipo del valore letterale 3.14159 come float. Il compilatore usa il tipo di pi per determinare il tipo dell'espressione length * pi * radius * radius come float. Di conseguenza, il tipo restituito complessivo della funzione è float.

Per specificare il tipo restituito in modo esplicito, scrivere il codice come segue:

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

Come scritto in precedenza, il compilatore applica float all'intera funzione; se si intende applicarlo anche ai tipi di parametro, usare il codice seguente:

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

Chiamata di una funzione

Per chiamare le funzioni, specificare il nome della funzione seguito da uno spazio e quindi qualsiasi argomento separato da spazi. Ad esempio, per chiamare la funzione cylinderVolume e assegnare il risultato al valore vol, si scrive il codice seguente:

let vol = cylinderVolume 2.0 3.0

Applicazione parziale degli argomenti

Se si specifica un numero inferiore al numero specificato di argomenti, si crea una nuova funzione che prevede gli argomenti rimanenti. Questo metodo di gestione degli argomenti viene definito currying ed è una caratteristica dei linguaggi di programmazione funzionale come F#. Si supponga, ad esempio, di lavorare con due dimensioni del tubo: uno ha un raggio di 2,0 e l'altro ha un raggio di 3,0. È possibile creare funzioni che determinano il volume della pipe 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

Specificare quindi l'argomento finale in base alle esigenze per varie lunghezze di tubo 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. È necessario specificare la parola chiave rec dopo la parola chiave let . Richiamare la funzione ricorsiva dall'interno del corpo della funzione esattamente come si richiama qualsiasi chiamata di funzione. La funzione ricorsiva seguente calcola il numero ndi Fibonacci. La sequenza di numeri di Fibonacci è nota sin 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 possono sovrapporre lo stack di programmi o eseguire in modo inefficiente se non vengono scritte con attenzione e con consapevolezza di tecniche speciali, ad esempio l'uso della ricorsione della coda, degli acceleratori e delle continuazioni.

Valori della funzione

In F# tutte le funzioni sono considerate valori; infatti, sono noti come valori di funzione. Poiché le funzioni sono valori, possono essere usate come argomenti per altre funzioni o in altri contesti in cui vengono usati i valori. Di seguito è riportato un esempio di funzione che accetta un valore di funzione come argomento:

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

Specificare il tipo di un valore di funzione usando il -> token . Sul lato sinistro di questo token è il tipo dell'argomento e sul lato destro è 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. Il codice seguente illustra come usare apply1:

let increment x = x + 1

let result1 = apply1 increment 100

Il valore di result sarà 101 dopo l'esecuzione del codice precedente.

Più argomenti sono 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, invece di definire funzioni denominate increment e mul, è possibile usare espressioni lambda come indicato di seguito:

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

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

Le espressioni lambda vengono definite usando la fun parola chiave . Un'espressione lambda è simile a una definizione di funzione, ad eccezione del fatto che invece del = token, il -> token viene usato per separare l'elenco di argomenti dal corpo della funzione. Come in una definizione di funzione regolare, i tipi di argomento possono essere dedotti o specificati in modo esplicito e il tipo restituito dell'espressione lambda viene dedotto dal tipo dell'ultima espressione nel corpo. Per altre informazioni, vedere Espressioni lambda: parola fun chiave.

Le pipeline

L'operatore pipe |> viene usato ampiamente durante l'elaborazione dei dati in F#. Questo operatore consente di stabilire "pipeline" di funzioni in modo flessibile. Pipelining consente di concatenare le chiamate di funzione come operazioni successive:

let result = 100 |> function1 |> function2

L'esempio seguente illustra come usare questi operatori per creare una pipeline funzionale semplice:

/// Square the odd values of the input and add one, using F# pipe operators.
let squareAndAddOdd values =
    values |> List.filter (fun x -> x % 2 <> 0) |> List.map (fun x -> x * x + 1)

let numbers = [ 1; 2; 3; 4; 5 ]

let result = squareAndAddOdd numbers

Il risultato è [2; 10; 26]. L'esempio precedente usa funzioni di elaborazione elenco, che illustrano come usare le funzioni per elaborare i dati durante la compilazione di pipeline. L'operatore pipeline stesso viene definito nella libreria principale F# come indicato di seguito:

let (|>) x f = f x

Composizione della funzione

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'operatore >> di composizione accetta due funzioni e restituisce una funzione. Al contrario, l'operatore |> della pipeline accetta un valore e una funzione e restituisce un valore. L'esempio di codice seguente mostra la differenza tra gli operatori di pipeline e composizione mostrando le differenze nelle firme e nell'utilizzo delle funzioni.

// 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
// ( |> ) : 'T1 -> ('T1 -> 'U) -> 'U
let Pipeline2 x = addOne x |> timesTwo

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

// Result is 5
let result3 = Pipeline1 2

// Result is 6
let result4 = Pipeline2 2

Overload di funzioni

È possibile eseguire l'overload di metodi di un tipo ma non di funzioni. Per altre informazioni, vedere Metodi.

Vedere anche