Functions are values. A function is defined with let, takes its arguments one after the other (curried), and is called by writing the arguments after its name, separated by spaces.
Defining and calling
let add x y = x + y // int -> int -> int
let result = add 1 2 // call: arguments follow the name
let inc = add 1 // partial application: int -> int
let greet () = printfn "hello" // a function of unit
let swap (a, b) = (b, a) // one tupled argument
let typed (x: int) (y: int) : int = x + y
let twice f x = f (f x) // a higher-order function
let r = twice (fun n -> n * 2) 5 // a lambda: 20
let sq = fun x -> x * x // a lambda bound to a name
let describe = function // a lambda that matches its argument
| 0 -> "zero"
| _ -> "other"
f x + 1 is (f x) + 1.f a bis(f a) b. Giving fewer arguments than a function has returns a function waiting for the rest (partial application).let f () = …takesunit; the call isf (). Without the(),let f = …is a value, not a function.- A bracketed argument list is one tuple argument:
swap (1, 2). Members of classes usually take tupled arguments, which allows overloading, named arguments and optional arguments (f(a, b = 2)). f(x)with no space is a high-precedence application, sof(x).Lengthandg x(y)parse differently from their spaced forms.- A function can be defined inside another and then is a closure: it uses the values of the enclosing scope, which stay alive as long as the function does. A
mutablelocal cannot be captured; useref.
Pipelines and composition
[ 1 .. 10 ]
|> List.filter (fun n -> n % 2 = 0) // forward pipe: x |> f is f x
|> List.map (fun n -> n * n)
|> List.sum
|> printfn "%d"
let pipeline = List.filter ((>) 5) >> List.map string // composition: f >> g is fun x -> g (f x)
let bump = printfn "%d" << (+) 1 // <<: g << f is fun x -> g (f x)
let lengths = ["a"; "bb"] |> List.map _.Length // F# 8: _.Length
|>, ||> and |||> pipe one, two or three values (the last two take tuples); <|, <|| and <||| are the backward forms; >> and << compose. An operator is a function: (+) is fun a b -> a + b, and (+) 1 is a partial application.
Recursion and tail calls
let rec factorial n = if n <= 1 then 1 else n * factorial (n - 1)
let rec isEven n = if n = 0 then true else isOdd (n - 1)
and isOdd n = if n = 0 then false else isEven (n - 1)
let sumTo n =
let rec loop acc i = // a tail call: no stack growth
if i > n then acc else loop (acc + i) (i + 1)
loop 0 1
[<TailCall>]
let rec count acc = function [] -> acc | _ :: t -> count (acc + 1) t
rec is needed for a function to call itself; and joins mutually recursive functions.A call in tail position is compiled so that it does not grow the stack, which is how loops are written. [<TailCall>] asks the compiler to warn when a call is not a tail call; F# 9 warns when it is put on a function that is not recursive. The compiler also has a warning for recursive functions that are not tail-recursive (F# 8).
Inline, operators and special forms
let inline f x = …inlines the body into each call, and allows statically resolved type parameters (see generics).[<InlineIfLambda>]on a function-typed parameter inlines a lambda argument (F# 6).- Operators are defined as functions in brackets:
let (|>>) a f = f a,let (+++) a b = a + b. An operator that starts with*needs spaces:( *** ), so that(*is not read as a comment. [<ParamArray>]makes a method take any number of arguments;?argmakes one optional (anoptioninside);[<Optional; DefaultParameterValue(0)>]makes a .NET-style optional argument.do exprat the top of a module runs an expression when the module is initialised.externdeclares a native function (see interop).[<EntryPoint>] let main argv = …is the explicit program entry; it has typestring[] -> int(see file structure).- A function value that has a type annotation with a name (
f: x: int -> int) gives names to the arguments for documentation.
Not described
Known, not yet covered
- Reference: memoisation and function values in records are only mentioned; the rules for automatic generalisation of functions (the value restriction), closures over struct values and byref parameters, and the exact precedence of high-precedence application were not described; the whole
Operatorsfunction list is on the library page. - Reference: the documentation pages for functions, let bindings, recursive functions, inline functions and the entry point were read in part (the entry point page in full); the other examples were written for this page and not compiled.
See also
- Expressions and control flow
- Computation expressions –
asyncandtaskfunctions