2010-04-27 10 views
19

que tiene una porción de código:F # función de llamar la confusión sintaxis

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

Qué quería volver a escribir a:

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

pero no parece el F # compilador gustaría. Yo tenía la impresión de que estas dos llamadas a funciones eran intercambiables:

f (a, b) 
(f a b) 

El error que consigo es:

El miembro o constructor de objetos 'GetAttributeValue' tomando 2 argumentos no son accesibles desde el código ubicación. Todas las versiones accesibles del método 'GetAttributeValue' toman 2 argumentos.

Lo que parece entretenido, ya que parece indicar que necesita lo que le estoy dando. ¿Que me estoy perdiendo aqui?

Respuesta

53

Una llamada a la función habitual en F # se escribe sin paréntesis y los parámetros están separados por espacios. La forma más sencilla de definir una función de varios parámetros es escribir esto:

let add a b = a + b 

Como se señaló Pascal, esta forma de parámetros que especifican se llama currificación - la idea es que una función toma un solo parámetro y el resultado es una función que toma el segundo parámetro y devuelve el resultado real (u otra función). Al llamar a una función simple como esta, escribiría add 10 5 y el compilador (en principio) interpreta esto como ((add 10) 5). Esto tiene algunas ventajas agradables - por ejemplo, que le permite utilizar aplicación de función parcial donde se especifica sólo unos primeros argumentos de una función:

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

Esta característica es útil en la práctica, por ejemplo, en el tratamiento de listas:

// The code using explicit lambda functions.. 
[ 1 .. 10 ] |> List.map (fun x -> add 10 x) 

// Can be rewritten using partial function application: 
[ 1 .. 10 ] |> List.map (add 10) 

Ahora, vamos a la parte confusa - en F #, también puedes trabajar con tuplas, que son tipos de datos simples que te permiten agrupar múltiples valores en valores individuales (ten en cuenta que las tuplas no están relacionadas con funciones de cualquier manera).Por ejemplo, puede escribir:

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" 

Cuando se escribe una función que toma los parámetros entre paréntesis, separados por una coma, en realidad estás escribiendo una función que toma un único parámetro que es 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 

Esta es una función de un tipo diferente - toma un solo parámetro que es una tupla, mientras que la primera versión era una función que tomaba dos parámetros (usando currying).

En F #, la situación es un poco más complicada que eso: los métodos .NET aparecen como métodos que toman una tupla como parámetro (para que pueda llamarlos con la notación entre paréntesis), pero son algo limitados (por ejemplo, no se puede crear una tupla primero y luego llamar al método dándole solo la tupla). Además, el código F # compilado en realidad no produce métodos en la forma al curry (por lo que no puede usar la aplicación de función parcial directamente desde C#). Esto se debe a razones de rendimiento: la mayoría de las veces, especifica todos los argumentos y esto se puede implementar de manera más eficiente.

Sin embargo, el principio es que una función toma múltiples parámetros o toma una tupla como parámetro.

+6

Gran respuesta, pero tenga en cuenta que su penúltimo párrafo no es del todo correcto: es perfectamente posible pasar una tupla no sintáctica a un método .NET (por ejemplo, 'let t = 1,2 en Object.ReferenceEquals t') . Sin embargo, esto no funciona para los métodos sobrecargados. – kvb

+1

@kvb: Buen punto: sabía que había algunas limitaciones, pero no estaba exactamente seguro. Gracias por la aclaración. –

+3

¡Impresionante! Respuestas como esta es lo que hace que StackOverflow sea un lugar tan valioso. Gracias Thomas. – Daniel

3

En f (a,b), f debe ser una función que toma un solo argumento (que es un par).

En f a b, que es corto para (f a) b, f cuando se aplica a a devuelve una función que se aplica a b.

Ambas son formas casi equivalentes de pasar argumentos a una función, pero no se puede usar una función diseñada para un estilo con la otra. El segundo estilo se llama "currying". Tiene la ventaja de permitir que se realicen algunos cálculos tan pronto como se apruebe a, especialmente si va a usar el mismo a con diferentes b s. En este caso se puede escribir:

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

Así (FAB) no va a funcionar para cualquiera de las otras funciones .Net Lang, ya que no creo que ninguno de ellos se crean para ser curry? – Daniel

+0

@Daniel: sospecho que si define una función 'f a b' en F #, podría llamarla' f (a) (b) 'en, por ejemplo, C# (ya que eso es exactamente lo que 'f a b' significa como lo señaló Pascal). – sepp2k

+0

@ sepp2k: Quise decir que todas las llamadas a funciones creadas originalmente en C#, VB y tal vez algunas de las otras lenguas .net tienen la forma f (a, b), ya que esos otros lenguajes no admiten el currying. – Daniel

2

Para responder a su pregunta implícita, en este tipo de situación, puede ser útil para escribir una pequeña función auxiliar:

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

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

y dependiendo de cómo desea utilizarlo, podría ser más útil el curry es al revés ':

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 
Cuestiones relacionadas