Condividi tramite


Funzioni

Le funzioni sono l'unità fondamentale di esecuzione di un programma in qualsiasi linguaggio di programmazione. Come negli altri linguaggi, una funzione F# ha un nome, può avere parametri e accettare argomenti e ha un corpo. F# supporta anche i costrutti di programmazione funzionale, ad esempio l'uso di funzioni come valori, l'uso di funzioni senza nome nelle espressioni, composizione di funzioni per creare nuove funzioni, funzioni sottoposte a currying e la definizione implicita di funzioni attraverso l'applicazione parziale di argomenti di funzioni.

Le funzioni vengono definite tramite la parole chiave let oppure, se la funzione è ricorsiva, tramite la combinazione di parole chiave let rec.

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

Function-name (nome_funzione) è un identificatore che rappresenta la funzione. Parameter-list (elenco_parametri) consiste di parametri successivi separati da spazi. È possibile specificare un tipo esplicito per ogni parametro, come illustrato nella sezione Parametri. Se non si specifica un tipo di argomento specifico, il compilatore prova a dedurre il tipo dal corpo della funzione. Function-body (corpo_funzione) è costituito da un'espressione. L'espressione che costituisce il corpo della funzione è in genere un'espressione composta che consiste di un numero di espressioni che terminano con un'espressione finale, che è il valore restituito. Return-type (tipo_restituito) è rappresentato dai due punti seguiti 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 inline. Per informazioni su inline, vedere Funzioni inline.

Ambito

A qualsiasi livello di ambito diverso dall'ambito del modulo, non è un errore riusare un nome di funzione o un valore. Se si riusa un nome, il nome dichiarato successivamente sostituisce il nome dichiarato in precedenza. Tuttavia, nell'ambito di livello superiore in 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

Ma 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 vengono 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 segue il nome del parametro ed è separato dal nome da due punti. Se si omette il tipo per il parametro, questo viene dedotto dal compilatore. Ad esempio, nella definizione della funzione seguente, l'argomento x viene dedotto come tipo int perché 1 è di tipo int.

let f x = x + 1

Tuttavia, il compilatore proverà a creare la funzione nel modo più generico possibile. Ad esempio, si noti 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 funzioni

Un corpo di funzione può contenere definizioni di funzioni e variabili locali. Tali funzioni e variabili rientrano nell'ambito del corpo della funzione corrente ma non al suo esterno. È necessario usare il rientro per indicare che una definizione si trova in un corpo di 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 Code Formatting Guidelines (Linee guida per la formattazione del codice) e Verbose Syntax (Sintassi dettagliata).

Valori restituiti

Il compilatore usa l'espressione finale in un corpo di funzione per determinare il valore restituito e il tipo. Il compilatore può dedurre il tipo dell'espressione finale dalle espressioni precedenti. Nella funzione cylinderVolume, come illustrato nella sezione precedente, il tipo di pi è determinato dal tipo di valore letterale 3.14159 come float. Per determinare il tipo dell'espressione length * pi * radius * radius come float, il compilatore usa il tipo di pi. Pertanto, il tipo restituito completo della funzione è float.

Per specificare il tipo restituito in modo esplicito, 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

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

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

Chiamata di una funzione

Le funzioni vengono chiamate specificando il nome della funzione seguito da uno spazio e quindi dagli argomenti separati da spazi. Ad esempio, per chiamare la funzione cylinderVolume e assegnare il risultato al valore vol, scrivere il codice seguente:

let vol = cylinderVolume 2.0 3.0

Applicazione parziale degli argomenti

Se si specifica un numero di argomenti inferiore al numero specificato, si crea una nuova funzione che prevedere gli argomenti rimanenti. Questo metodo di gestione degli argomenti è detto currying ed è una caratteristica dei linguaggi di programmazione funzionale come F#. Ad esempio, si supponga di usare due dimensioni per un tubo: una ha un raggio di 2 e l'altra ha un raggio di 3. È possibile creare funzioni che determinano il volume del tubo nel modo seguente:

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

Si specifica quindi l'argomento finale in base alle esigenze per varie lunghezze di pipe 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. Richiedono di specificare la parola chiave rec seguita dalla parola chiave let. È possibile 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 dei numeri di Fibonacci è nota dall'antichità ed è una sequenza in cui ogni numero successivo è la somma di 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 sovraccaricare lo stack di programmi o eseguire in modo inefficiente se non le scrivi con cura e con consapevolezza delle tecniche speciali, ad esempio l'uso della ricorsione della coda, gli accumulatori e le continuazioni.

Valori della funzione

In F#, tutte le funzioni sono considerate valori, e infatti sono note 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 valori. Di seguito è incluso un esempio di una funzione che accetta un valore di funzione come argomento:

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

Il tipo di valore di una funzione viene specificato usando il token ->. Sul lato sinistro di questo token si trova il tipo dell'argomento e sul lato destro si trova il valore restituito. Nell'esempio precedente, apply1 è una funzione che accetta una funzione transform come argomento, in cui transform è una funzione che accetta un numero intero e restituisce un altro numero intero. L'esempio di 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 le funzioni denominate increment e mul, è possibile usare espressioni lambda nel modo seguente:

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 parola chiave fun. Un'espressione lambda è simile a una definizione di funzione, con la differenza che al posto del token =, viene usato il token -> per separare l'elenco di argomenti dal corpo della funzione. Analogamente a una definizione di funzione regolare, i tipi di argomenti 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 chiave fun.

Pipelines

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 semplice pipeline funzionale:


/// 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 è possibile usare le funzioni per elaborare i dati durante la compilazione di pipeline. L'operatore della pipeline stesso è definito nella libreria core F# come segue:

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 illustra la differenza tra gli operatori di pipeline e di composizione visualizzando le differenze nelle firme di funzione e nell'uso.

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

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

Vedi anche