Definition style preferences

Viewed 62

What is the preferable style for F# definitions?

The book I am studying makes regular use the following style:

let foo = fun x y -> 
    let aux1 = fun z -> z * 2 
        in aux1 x + 
    let aux2 = fun q -> q * 3 
        in aux2 y;;

On the other side, when I look for information on the web, it is most likely to meet something like:

let foo (x: int) (y: int) =
    let aux1 (z:int) = z * 2
        in aux1 x +
    let aux2 (q: int) = q * 3
        in aux2 y;;

On the Guide I failed to find a reference about it. Is it a matter that goes beyond "mere style"? There are efficiency implications behind these two approaches?

What does your experience suggest?

2 Answers

As a general rule, F# function definitions tend to do one of two things:

  1. Define as few types as possible (let foo x y = ...). This is the case for most functions. Or...

  2. Explicitly define the types of each argument and the return type (let foo (x : int) (y : int) : int = ....

Style #2 is rare, and I've usually seen it for functions that are explicitly part of the API of a module, and that have /// comments to provide documentation as well. For internal functions, though, the typeless variant is usually used, since F#'s type inference works so well.

Also, as s952163 pointed out in a comment, the in keyword is almost never used anymore, since the #light style makes it unnecessary. I'd expect to see your sample code written as follows in modern F# style:

let foo x y =
    let aux1 z = z * 2
    let aux2 q = q * 3
    (aux1 x) + (aux2 y)

No ;; necessary, either, unless you're typing into the F# Interactive console. If you're using VS Code + Ionide, and highlighting segments of code and pressing Alt + Enter to send them to F# Interactive, then you don't need any ;; separators because Ionide adds them automatically.

I found evidence suggesting that the first style, even if today unconventional, is intrinsically connected to currying and anonymous functions.

Currying is a powerful characteristic of F#, where, I remember, every function could take only one parameter. For example:

let add x y = x + y 
val add: int -> int -> int

The signature is interpreted as add is a function that takes two integers as input and return an integer. When compile time comes, the function is interpreted like:

let add2 = fun x -> fun y -> x + y
val add2: int -> int -> int

where val add2: int -> int -> int is semantically equivalent to val add: (int -> (int -> int))

By providing an argument to add2, such as 6, it returns fun y -> 6 + y, which is another function waiting for its argument, while x is replaced by 6. Currying means that every argument actually returns a separate function: that's why when we call a function with only few of its parameters returns another function.

If I got it correctly, the more common F# syntax of the second example, let add x y = x + y, could be thought like syntactic sugar for the explicit currying style shown above.

Related