开发者

F# function calling syntax confusion

I have a piece of code:

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

Which I wanted to rewrite to:

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

But the F# compiler doesn't seem to like tha开发者_Python百科t. I was under the impression that these two function calls were interchangeable:

f (a, b)
(f a b)

The error that I get is:

The member or object constructor 'GetAttributeValue' taking 2 arguments are not accessible from this code location. All accessible versions of method 'GetAttributeValue' take 2 arguments.

Which seems amusing, as it seems to indicate that it needs what I'm giving it. What am I missing here?


A usual function call in F# is written without parentheses and parameters are separated by spaces. The simple way to define a function of several parameters is to write this:

let add a b = a + b

As Pascal noted, this way of specifying parameters is called currying - the idea is that a function takes just a single parameter and the result is a function that takes the second parameter and returns the actual result (or another function). When calling a simple function like this one, you would write add 10 5 and the compiler (in principle) interprets this as ((add 10) 5). This has some nice advantages - for example it allows you to use partial function application where you specify only a first few arguments of a function:

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

This feature is practically useful for example when processing lists:

// 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) 

Now, let's get to the confusing part - in F#, you can also work with tuples, which are simple data types that allow you to group multiple values into a single values (note that tuples aren't related to functions in any way). You can for example write:

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"

When you write a function that takes parameters in parentheses separated by a comma, you're actually writing a function that takes a single parameter which is a tuple:

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

This is a function of a different type - it takes a single parameter which is a tuple, while the first version was a function that took two parameters (using currying).

In F#, the situation is a bit more complicated than that - .NET methods appear as methods that take a tuple as a parameter (so you can call them with the parenthesized notation), but they are somewhat limited (e.g. you cannot create a tuple first and then call the method giving it just the tuple). Also, the compiled F# code doesn't actually produce methods in the curried form (so you cannot use partial function application directly from C#). This is due to performance reasons - most of the times, you specify all arguments and this can be implemented more efficiently.

However, the principle is that a function either takes multiple parameters or takes a tuple as a parameter.


In f (a,b), f must be a function that takes a single argument (which is a pair).

In f a b, which is short for (f a) b, f when applied to a returns a function that is applied to b.

Both are almost equivalent ways to pass arguments to a function, but you cannot use a function designed for one style with the other. The second style is called "currying". It has the advantage of allowing some computations to be made as soon as a is passed, especially if you are going to use the same a with different bs. In this case you can write:

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


To answer your implicit question, in this kind of situation, it can be useful to write a little helper function:

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

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

and depending on how you want to use it, it might be more useful to 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


A very simple explanation; there are two function calls in F#: curried and tupled.

open System 

// curried
let add x y = x + y 
add 5 <| 10 |> Console.WriteLine
(add 5) 10 |> Console.WriteLine
(add 5) <| 10 |> Console.WriteLine
add <| 5 <| 10 |> Console.WriteLine
add 5 10 |> Console.WriteLine
5 |> add <| 10 |> Console.WriteLine
Console.WriteLine(add 10 5)
Console.WriteLine((add 10) 5)
Console.WriteLine((add 10) <| 5)
Console.WriteLine((add <| 10 <| 5))

// tupled
let add'(x, y) = x + y

add'(10, 5) |> Console.WriteLine
add' <| (10, 5) |> Console.WriteLine
(10, 5) |> add' |> Console.WriteLine
Console.WriteLine(add'(10, 5))
Console.WriteLine(((10, 5) |> add'))
Console.WriteLine((add' <| (10, 5)))

In a curried form, we can pass values one by one; in tupled form, we pass only tuples -- pairs of values.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜