2010-04-27 10 views
19

Ho un pezzo di codice:F funzione # chiamando sintassi confusione

links 
    |> Seq.map (fun x -> x.GetAttributeValue ("href", "no url")) 

che ho voluto riscrivere a:

links 
    |> Seq.map (fun x -> (x.GetAttributeValue "href" "no url")) 

Ma il compilatore F # non sembrano come questo. Ho avuto l'impressione che queste due chiamate di funzione sono intercambiabili:

f (a, b) 
(f a b) 

L'errore che ottengo è:

Il membro oi costruttore dell'oggetto 'GetAttributeValue' l'assunzione di 2 argomenti non sono accessibili da questo codice Posizione. Tutte le versioni accessibili del metodo 'GetAttributeValue' accettano 2 argomenti.

Che sembra divertente, come sembra indicare che ha bisogno di quello che sto dando. Cosa mi manca qui?

risposta

53

Una normale chiamata di funzione in F # viene scritta senza parentesi e i parametri sono separati da spazi. Il modo più semplice per definire una funzione di diversi parametri è scrivere questo:

let add a b = a + b 

Come osservato Pascal, questo modo di parametri che specificano è chiamato currying - l'idea è che una funzione richiede un solo parametro e il risultato è una funzione che accetta il secondo parametro e restituisce il risultato effettivo (o un'altra funzione). Quando si chiama una funzione semplice come questa, si dovrebbe scrivere add 10 5 e il compilatore (in linea di principio) lo interpreta come ((add 10) 5). Questo ha alcune belle vantaggi - per esempio, consente di utilizzare applicazione funzione parziale in cui si specifica solo un primo paio di argomenti di una funzione:

let addTen = add 10 // declares function that adds 10 to any argument 
addTen 5 // returns 15 
addTen 9 // returns 19 

Questa funzione è praticamente utile ad esempio quando si elaborano liste:

Ora, passiamo alla parte confusa - in F #, puoi anche lavorare con tuple, che sono tipi di dati semplici che ti permettono di raggruppare più valori in un singolo valore (nota che le tuple non sono legate alle funzioni in ogni modo).È possibile ad esempio scrivere:

let tup = (10, "ten") // creating a tuple 
let (n, s) = tup   // extracting elements of a tuple using pattern 
printfn "n=%d s=%s" n s // prints "n=10 s=ten" 

Quando si scrive una funzione che accetta parametri tra parentesi separati da una virgola, si sta effettivamente scrivendo una funzione che prende un solo parametro che è una tupla:

// The following function: 
let add (a, b) = a * b 

// ...means exactly the same thing as: 
let add tup = 
    let (a, b) = tup // extract elements of a tuple 
    a * b 

// You can call the function by creating tuple inline: 
add (10, 5) 
// .. or by creating tuple in advance 
let t = (10, 5) 
add t 

Questa è una funzione di un tipo diverso: prende un singolo parametro che è una tupla, mentre la prima versione era una funzione che prendeva due parametri (usando la scrittura).

In F #, la situazione è un po 'più complicata di quella. I metodi .NET appaiono come metodi che accettano una tupla come parametro (quindi puoi chiamarli con la notazione tra parentesi), ma sono un po' limitati (es. non è possibile creare prima una tupla e quindi chiamare il metodo dandogli solo la tupla). Inoltre, il codice F # compilato in realtà non produce metodi nel formato al curry (quindi non è possibile utilizzare l'applicazione di funzione parziale direttamente da C#). Ciò è dovuto a motivi di prestazioni - la maggior parte delle volte, si specificano tutti gli argomenti e questo può essere implementato in modo più efficiente.

Tuttavia, il principio è che una funzione prende più parametri o accetta una tupla come parametro.

+6

Ottima risposta, ma tieni presente che il penultimo paragrafo non è del tutto corretto: è perfettamente possibile passare una tupla non sintattica a un metodo .NET (ad esempio 'let t = 1,2 in Object.ReferenceEquals t') . Questo però non funziona con metodi sovraccaricati. – kvb

+1

@kvb: buon punto - Sapevo che c'erano alcune limitazioni, ma non ero esattamente sicuro. Grazie per il chiarimento. –

+3

Fantastico! Risposte come questa sono ciò che rende StackOverflow un posto così prezioso. Grazie Thomas. – Daniel

3

In f (a,b), f deve essere una funzione che accetta un singolo argomento (che è una coppia).

In f a b, che è l'abbreviazione di (f a) b, f se applicato a a restituisce una funzione che viene applicato al b.

Entrambi sono metodi quasi equivalenti per passare argomenti a una funzione, ma non è possibile utilizzare una funzione progettata per uno stile con l'altro. Il secondo stile è chiamato "currying". Ha il vantaggio di consentire alcuni calcoli da effettuare non appena viene passato a, soprattutto se si intende utilizzare lo stesso a con diversi s. In questo caso è possibile scrivere:

let f_a = f a (* computations happen now that a is available *) 
in 
f_a b1 .... f_a b2 .... 
+0

Quindi (fab) non funzionerà per nessuna delle altre funzioni .Net lang, poiché non credo che nessuno di loro sia stato creato per essere al curry? – Daniel

+0

@Daniel: sospetterei che se si definisce una funzione 'f a b' in F #, si potrebbe chiamarla' f (a) (b) 'ad es. C# (come questo è esattamente ciò che 'f a b' significa come ha sottolineato Pascal). – sepp2k

+0

@ sepp2k: intendevo dire che tutte le chiamate a funzioni originariamente create in C#, VB e forse alcune delle altre. Lang ns sono di forma f (a, b), poiché tali altri linguaggi non supportano la scrittura. – Daniel

2

Per rispondere alla tua domanda implicita, in questo tipo di situazione, può essere utile scrivere un po 'di funzione di supporto:

let getAttrVal (x:TypeOfX) key default = x.GetAttributeValue(key, default) 

//usage 
links |> Seq.map (fun x -> getAttrVal x "href", "no url")) 

e seconda di come si desidera utilizzarlo, potrebbe essere più utile curry it 'backwards':

let getAttrVal key default (x:TypeOfX) = x.GetAttributeValue(key, default) 

//partial application 
let getHRef = getAttrVal "href" "no url" 

//usage 
links |> Seq.map (fun x -> getHRef x) 

//or, same thing: 
links |> Seq.map getHRef