Functions
Named functions
Declared a
let
binding (like a variable)Naming convention: camelCase
No
return
keyword: the function always returns the last expression in its bodyNo
()
around all parameters, no,
between parameters()
required around parameter with type annotation (1) or deconstruction (2)
let square x = x * x // Function with 1 parameter
let res = square 2 // Returns 4
// (1) Parentheses required for annotations of type
let square' (x: int) : int = x * x
// (2) Brackets required when deconstructing an object
// (here it's a single-case discriminated union 📍
let hotelId (HotelId value) = value
Functions of 2 or more parameters
Separate parameters and arguments with spaces:
// Function with 2 parameters
let add x y = x + y // val add: x: int -> y: int -> int
// Call with the 2 arguments
let res = add 1 2 // val res: int = 3
Inference
The inference of the add
function can be confusing: the +
works for any numbers and for strings too, but add
is limited to int
! To get it work, we need to write let inline add ...
→ Related to a special kind of generics: statically resolved type parameters (SRTP) 📍
⚠️️ ,
creates another kind of functions using tuples 📍
let addByPair (x, y) = x + y
// val addByPair: x: int * y: int -> int
Functions without parameter
Use ()
(like in C#)
let printHello () = printfn "Hello"
// val printHello: unit -> unit
printHello ();;
// Hello
let notAFunction = printfn "Hello"
// Hello
// val notAFunction: unit = ()
☝️ unit
means "nothing" 📍
Multi-line function
Indentation required, but no need for {}
Can contain sub-function
let evens list =
let isEven x = // 👈 Sub-function
x % 2 = 0 // 💡 `=` equality operator - No `==` operator in F♯
List.filter isEven list
// val evens: list: int list -> int list
let res = evens [1;2;3;4;5]
// val res: int list = [2; 4]
Anonymous function
A.k.a. Lambda, arrow function
Syntax:
fun {parameters} -> body
(≠ in C♯{parameters} ⇒ body
)In general,
()
required all around, for precedence reason
let evens' list = List.filter (fun x -> x % 2 = 0) list
_.Member shorthand (F♯ 8)
type Person = { Name: string; Age: int }
let people =
[ { Name = "Alice"; Age = 30 }
{ Name = "Billy"; Age = 5 } ]
// Regular lambda (Shorthand not possible)
let adults = people |> List.filter (fun person -> person.Age >= 18)
// val adults: Person list = [{ Name = "Alice"; Age = 30 }]
// Member chain shorthand
let uppercaseNames = people |> List.map _.Name.ToUpperInvariant() // 👈👈
// val uppercaseNames: string list = ["ALICE"; "BILLY"]
Naming convention related to functions
It's usual in F♯ to use short names:
x
,y
,z
: parameters for values of the same typef
,g
,h
: parameters for input functionsxs
: list ofx
_
: discard an element not used (like in C♯ 7.0)
☝️ Suited for a short function body or for a generic function:
// Function that simply returns its input parameter, whatever its type
let id x = x
// Composition of 2 functions
let compose f g = fun x -> g (f x)
🔗 When x, y, and z are great variable names by Mark Seemann
Piping
Pipe operator |>
: same idea that in UNIX with |
→ value |> function
send a value to a function
→ match left-to-right reading order: "subject verb"
→ same order than with OOP: object.method
let a = 2 |> add 3 // to read "2 + 3"
// We pipe a list to the "List.filter predicate" function
let evens = [1;2;3;4;5] |> List.filter (fun x -> x % 2 = 0)
// ≃ C♯
var a = 2.Add(3);
var nums = new[] { 1, 2, 3, 4, 5 };
var evens = nums.Where(x => x % 2 == 0);
Pipeline: chain of pipings
Style of coding to emphasize the data flowing from functions to functions → without intermediary variable 👍
Similar to a built-in fluent API → no need to return the object at the end of each method 👍
// Short syntax: in a single line fitting the screen width
let res = [1;2;3;4;5] |> List.filter (fun x -> x % 2 = 0) |> List.sum
// More readable with line breaks
let res' =
[1; 2; 3; 4; 5]
|> List.filter isOdd // With `let isOdd x = x % 2 <> 0`
|> List.map square // `let square x = x * x`
|> List.map addOne // `let addOne x = x + 1`
If/then/else expression
In F♯, if/then(/else)
is an expression, not a statement, so every branch (then
and else
) should return a value and both returned values should be type-compatible.
let isEven n =
if n % 2 = 0 then
"Even"
else
"Odd"
💡 if b then x else y
≃ C♯ ternary operator b ? x : y
☝ When then
returns "nothing", else
is optional:
let printIfEven n msg =
if n |> isEven then
printfn msg
💡 We can use elif
keyword instead of else if
.
Match expression
let translateInFrench civility =
match civility with
| "Mister" -> "Monsieur"
| "Madam" -> "Madame"
| "Miss" -> "Mademoiselle"
| _ -> "" // 👈 wilcard `_`
Equivalent in C♯ 8 :
public static string TranslateInFrench(string civility) =>
civility switch {
"Mister" => "Monsieur"
"Madam" => "Madame"
"Miss" => "Mademoiselle"
_ => ""
}
Last updated
Was this helpful?