2
votes

How can I call F# functions by specifying the parameter names in the call site?

I've tried the following:

let add x y =
    x + y

add (x = 10) (y = 10) // How to specify the name x and y when calling add?

But it gives this error:

error FS0039: The value or constructor 'x' is not defined.

3

3 Answers

2
votes

You can't invoke let-bound functions with named arguments. It's allowed only for methods in classes

Named arguments are allowed only for methods, not for let-bound functions, function values, or lambda expressions.

Documentation


Technically you can declare static class and use method from it, but I think it's wrong. Just wrong. Don't do it

[<AbstractClass; Sealed>]
type MathOperations =
    static member Add (x, y) = x + y

open type MathOperations

[<EntryPoint>]
let main argv =
    Add(x = 3, y = 4)
    |> printfn "%d"
    0
2
votes

This issue confused me too in the beginning. Functions and methods are not alike.

  • Functions are curried by default to support partial application. They don't support named argument calls.
  • Methods support named arguments using a single tuple for all parameters.
    • (EDIT after Bent Tranberg's comment) Methods also support curried declaration member _.Add x y = x + y and even mixed style declaration member _.Add3 (x: int, y: int) (z: int) = x + y + z but these methods cannot be called using named arguments o.Add3 (y=2, x=1) 3 💥

In JavaScript, named arguments can be simulated using an object literal as argument. Let's try do the same in F#, using tuple or record:

1. Tuple: Method arguments are provided all at once with a tuple. Can we use a tuple as function parameter and call the function with named arguments? No ❌

let add (x, y) = x + y

add (x = 1, y = 2) // 💥
//   ~ Error FS0039: The value or constructor 'x' is not defined

2. Anonymous record: not possible due to actual limitation in anonymous record deconstruction ❌

let add {| X = x; Y = y |} = x + y
//      ~~ Error FS0010: Unexpected symbol '{|' in pattern

3. Named record: ⚠️

type AddParam = { x: int; y: int }

let add { x = x; y = y } = x + y

add { x = 1; y = 2 }

This way, we are able to name the arguments and choose their order ({ y = 2; x = 1 } but we loose the partial application. So it's not idiomatic→ should be avoided, unless in specific cases.

4. Single-case DU: 👌

If we care only about the argument naming but not about the argument re-ordering, we can use discriminated unions, especially single-case DU:

type XParam = X of int
type YParam = Y of int

let add (X x) (Y y) = x + y

add (X 1) (Y 2)

This way, the arguments appeared as named and partial application is preserved.

--

EDIT

☝ I don't recommend creating single-case DU just to simulate named arguments ! It's the other way around: when we have single-case DU in our domain model, we know that they will bring their semantic to document the code and help reasoning about the code, in this case more or less like named arguments.

1
votes

You have to understand function calling in F#, which is different from traditional imperative languages like C#.

let f x y = x + y // defines a function with this signature:
val f : x:int -> y:int -> int

// this function is full or partially applicable:

let g = f 4 // partial application
val g : (int -> int) // a new function that evaluates f 4 y
g 10
val it : int = 14

In traditional languages functions have a single set of arguments. In F# you express such traditional functions with multiple arguments into a function with a tuple argument:

let f (x, y) = x + y
val f : x:int * y:int -> int
// partial application is no longer possible, since the arguments have been packed into a single tuple

Such traditional tuple-argument functions do not allow calls with named arguments:

f (x=3, y=5) // interpreted as = test expressions

F# language design is sound here, and (x=3, y=5) does not express the desired tuple.

F# has named arguments since a while (this was added in the last years sometime). This is limited to member functions however. Once you translate your function into a member it works:

type A() =
    static member f (x,y) = x + y

A.f(y=3, x=4) // yes, this works!