Ricerca…
Funzioni di più di un parametro
In F #, tutte le funzioni richiedono esattamente un parametro . Questa sembra un'affermazione strana, dal momento che è banalmente facile dichiarare più di un parametro in una dichiarazione di funzione:
let add x y = x + y
Ma se si digita la dichiarazione di funzione nell'interprete interattivo F #, vedrete che la sua firma di tipo è:
val add : x:int -> y:int -> int
Senza i nomi dei parametri, quella firma è int -> int -> int
. L'operatore ->
è right-associative, il che significa che questa firma è equivalente a int -> (int -> int)
. In altre parole, add
è una funzione che accetta un parametro int
e restituisce una funzione che accetta un int
e restituisce int
. Provalo:
let addTwo = add 2
// val addTwo : (int -> int)
let five = addTwo 3
// val five : int = 5
Tuttavia, puoi anche chiamare una funzione come add
in un modo più "convenzionale", passandogli direttamente due parametri, e funzionerà come ti aspetteresti:
let three = add 1 2
// val three : int = 3
Questo vale per le funzioni con tutti i parametri che vuoi:
let addFiveNumbers a b c d e = a + b + c + d + e
// val addFiveNumbers : a:int -> b:int -> c:int -> d:int -> e:int -> int
let addFourNumbers = addFiveNumbers 0
// val addFourNumbers : (int -> int -> int -> int -> int)
let addThreeNumbers = addFourNumbers 0
// val addThreeNumbers : (int -> int -> int -> int)
let addTwoNumbers = addThreeNumbers 0
// val addTwoNumbers : (int -> int -> int)
let six = addThreeNumbers 1 2 3 // This will calculate 0 + 0 + 1 + 2 + 3
// val six : int = 6
Questo metodo di pensare alle funzioni multiparametro è come funzioni che accettano un parametro e restituiscono nuove funzioni (che a loro volta possono prendere un parametro e restituire nuove funzioni, finché non si raggiunge la funzione finale che prende il parametro finale e infine restituisce un risultato) Si chiama curry , in onore del matematico Haskell Curry, che è famoso per lo sviluppo del concetto. (È stato inventato da qualcun altro, ma Curry merita meritatamente la maggior parte del merito.)
Questo concetto è usato in F # e vorrete avere familiarità con esso.
Nozioni di base sulle funzioni
La maggior parte delle funzioni in F # vengono create con la sintassi let
:
let timesTwo x = x * 2
Definisce una funzione denominata timesTwo
che accetta un singolo parametro x
. Se si esegue una sessione F # interattiva ( fsharpi
su OS X e Linux, fsi.exe
su Windows) e si incolla la funzione in (e si aggiunge il ;;
che dice a fsharpi
di valutare il codice appena digitato), vedrai che risponde con:
val timesTwo : x:int -> int
Ciò significa che timesTwo
è una funzione che accetta un singolo parametro x
di tipo int
e restituisce un int
. Le firme delle funzioni vengono spesso scritte senza i nomi dei parametri, quindi vedrai spesso questo tipo di funzione scritto come int -> int
.
Ma aspetta! In che modo F # sapeva che x
era un parametro intero, dal momento che non hai mai specificato il suo tipo? Questo è dovuto al tipo di inferenza . Perché nel corpo della funzione, hai moltiplicato x
per 2
, i tipi di x
e 2
devono essere uguali. (Come regola generale, F # non invierà implicitamente i valori a tipi diversi, ma specificherà esplicitamente qualsiasi tipo di digitazione che si desidera).
Se vuoi creare una funzione che non assuma alcun parametro, questo è il modo sbagliato per farlo:
let hello = // This is a value, not a function
printfn "Hello world"
Il modo giusto per farlo è:
let hello () =
printfn "Hello world"
Questa funzione hello
ha l' unit -> unit
, che è spiegata nel tipo "unità" .
Funzioni al curry vs tupla
Esistono due modi per definire le funzioni con più parametri in F #, funzioni Curried e funzioni Tupled.
let curriedAdd x y = x + y // Signature: x:int -> y:int -> int
let tupledAdd (x, y) = x + y // Signature: x:int * y:int -> int
Tutte le funzioni definite da F # esterne (come il framework .NET) vengono utilizzate in F # con il modulo Tupled. La maggior parte delle funzioni nei moduli di base F # vengono utilizzate con il modulo Curried.
La forma Curried è considerata F # idiomatica, perché consente un'applicazione parziale. Nessuno dei seguenti due esempi è possibile con la forma Tupla.
let addTen = curriedAdd 10 // Signature: int -> int
// Or with the Piping operator
3 |> curriedAdd 7 // Evaluates to 10
La ragione è che la funzione Curried, quando viene chiamata con un parametro, restituisce una funzione. Benvenuto nella programmazione funzionale !!
let explicitCurriedAdd x = (fun y -> x + y) // Signature: x:int -> y:int -> int
let veryExplicitCurriedAdd = (fun x -> (fun y -> x + y)) // Same signature
Puoi vedere che è esattamente la stessa firma.
Tuttavia, quando si interfaccia con un altro codice .NET, come quando si scrivono le librerie, è importante definire le funzioni usando il modulo Tupla.
inlining
Inlining consente di sostituire una chiamata a una funzione con il corpo della funzione.
Questo a volte è utile per motivi di prestazioni su parte critica del codice. Ma la controparte è che il vostro assembly richiederà molto spazio poiché il corpo della funzione è duplicato ovunque si sia verificata una chiamata. Devi stare attento quando decidi se integrare una funzione o meno.
Una funzione può essere sottolineata con la parola chiave inline
:
// inline indicate that we want to replace each call to sayHello with the body
// of the function
let inline sayHello s1 =
sprintf "Hello %s" s1
// Call to sayHello will be replaced with the body of the function
let s = sayHello "Foo"
// After inlining ->
// let s = sprintf "Hello %s" "Foo"
printfn "%s" s
// Output
// "Hello Foo"
Un altro esempio con valore locale:
let inline addAndMulti num1 num2 =
let add = num1 + num2
add * 2
let i = addAndMulti 2 2
// After inlining ->
// let add = 2 + 2
// let i = add * 2
printfn "%i" i
// Output
// 8
Tubo avanti e indietro
Gli operatori di tubi sono utilizzati per passare parametri a una funzione in modo semplice ed elegante. Permette di eliminare i valori intermedi e facilitare la lettura delle chiamate di funzione.
In F # ci sono due operatori di pipe:
Inoltra (
|>
): passaggio dei parametri da sinistra a destralet print message = printf "%s" message // "Hello World" will be passed as a parameter to the print function "Hello World" |> print
Indietro (
<|
): passaggio dei parametri da destra a sinistralet print message = printf "%s" message // "Hello World" will be passed as a parameter to the print function print <| "Hello World"
Ecco un esempio senza operatori di pipe:
// We want to create a sequence from 0 to 10 then:
// 1 Keep only even numbers
// 2 Multiply them by 2
// 3 Print the number
let mySeq = seq { 0..10 }
let filteredSeq = Seq.filter (fun c -> (c % 2) = 0) mySeq
let mappedSeq = Seq.map ((*) 2) filteredSeq
let finalSeq = Seq.map (sprintf "The value is %i.") mappedSeq
printfn "%A" finalSeq
Possiamo abbreviare l'esempio precedente e renderlo più pulito con l'operatore pipe in avanti:
// Using forward pipe, we can eliminate intermediate let binding
let finalSeq =
seq { 0..10 }
|> Seq.filter (fun c -> (c % 2) = 0)
|> Seq.map ((*) 2)
|> Seq.map (sprintf "The value is %i.")
printfn "%A" finalSeq
Ogni risultato della funzione viene passato come parametro alla funzione successiva.
Se si desidera passare più parametri all'operatore del tubo, è necessario aggiungere un |
per ogni parametro aggiuntivo e creare una Tupla con i parametri. L'operatore di pipe Native F # supporta fino a tre parametri (|||> o <|||).
let printPerson name age =
printf "My name is %s, I'm %i years old" name age
("Foo", 20) ||> printPerson