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 destra

     let 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 sinistra

     let 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


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow