F# Module 2 – Expressions, values and built-in types
if, match and loops as expressions
Write F# if/then/else and match as expressions, fix FS0025 and FS0026, loop with for and while, replace break with recursion and avoid the F# 10 FS3873 warning.
What you will learn
- Write if/then/else and match as expressions whose branches share one type
- Fix warnings FS0025 and FS0026 by covering every case, in the right order
- Use for...in, for...to, for...downto and while loops, and replace break with recursion or List.tryFind
- Recognise warning FS3873 for a bare range in braces and write seq { } instead
Before you start
What you’ll learn · Before you start
What you will learn
- Write if/then/else and match as expressions whose branches share one type
- Fix warnings FS0025 and FS0026 by covering every case, in the right order
- Use for...in, for...to, for...downto and while loops, and replace break with recursion or List.tryFind
- Recognise warning FS3873 for a bare range in braces and write seq { } instead
Before you start
On this page
let label = if age >= 60 then "senior" does not compile: error FS0001 says that the if is missing an else
branch. In F#, if, match and even loops are expressions, so an if that produces a string must produce one in
every case, and a loop with break has nowhere to break to, because F# has no break.
You will write if and match as expressions whose branches share one type, read the two warnings that keep a
match honest, loop with for and while, search a list without break, and write ranges in the form F# 10
expects. Each rule comes with a run recorded on .NET 10, mistakes included.
if/then/else has a value, so every branch needs one
Here are four entries at the prompt, two of them refused:
from session import fsi
# if/then/else as an expression: a missing else, an if run for its effect, elif, and branches of two types.
fsi([
"let age = 70;;",
'let label = if age >= 60 then "senior";;',
'if age >= 60 then printfn "senior";;',
"let fare = if age < 5 then 0 elif age >= 60 then 40 else 100;;",
'let kind = if age >= 60 then "senior" else 0;;',
"#quit;;",
], args=["--nologo"]) Output
$ dotnet fsi --nologo > let age = 70;; val age: int = 70 > let label = if age >= 60 then "senior";; let label = if age >= 60 then "senior";; ------------------------------^^^^^^^^ stdin(2,31): error FS0001: This 'if' expression is missing an 'else' branch. Because 'if' is an expression, and not a statement, add an 'else' branch which also returns a value of type 'string'. > if age >= 60 then printfn "senior";; senior val it: unit = () > let fare = if age < 5 then 0 elif age >= 60 then 40 else 100;; val fare: int = 40 > let kind = if age >= 60 then "senior" else 0;; let kind = if age >= 60 then "senior" else 0;; -------------------------------------------^ stdin(5,44): error FS0001: All branches of an 'if' expression must return values implicitly convertible to the type of the first branch, which here is 'string'. This branch returns a value of type 'int'. > #quit;;
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 if_session.py
The F# conditional expressions reference states both rules. An if…then…else produces the value of the branch that
runs, so the types of the branches must match: "senior" in one branch and 0 in the other is error FS0001, all
branches must return the type of the first. And when there is no else, the type of the whole if is unit, so the
then branch must be unit too: if age >= 60 then printfn "senior" is fine, because printfn returns (), while
the "senior" branch of label is not. The compiler’s own message gives the reason: if is an expression, not a
statement. elif chains tests, as else if would; step through the fare for age 70:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- The first test. age is 70, so age < 5 is false and the then branch, 0, is not evaluated at all.
- The second test. elif age >= 60 is true, so the if takes this branch. Every branch is an int, so the if is an int.
- Later branches are skipped. Once a test is true, no later test runs, so the else branch, 100, is never looked at.
- The if has a value. The whole if/elif/else evaluates to 40, and let binds it: F# Interactive answered val fare: int = 40.
- Without an else. Leave out the else in let label = if age >= 60 then "senior" and the if has no value when the test is false: error FS0001 asks for an else branch of type string.
The four forms of if in this section, side by side:
| You write | Type | Result |
|---|---|---|
if ok then 1 else 2 |
int |
1 or 2 |
if ok then printfn "x" |
unit |
prints, or does nothing |
if ok then "x" |
none | error FS0001: missing else |
if ok then "x" else 0 |
none | error FS0001: branches differ |
Leaving a variable unset in one branch
Wrong: the C# habit of declaring let mutable fare = 0 and setting it inside an if without else. Right: bind the
value of the whole expression, let fare = if age < 5 then 0 elif age >= 60 then 40 else 100, so the compiler
proves that every path gives a fare.
Checkpoint
Check: if/then/else as an expression
match tries its rules in order and checks that none is missing
match compares a value with each rule in turn and takes the first one that fits. This script prices tickets with
an if chain and with a match, and prints both columns; the prompt then breaks three matches on purpose:
// One ticket-price rule written twice, as if/elif/else and as a match with guards. The prices are illustrative.
let priceWithIfs age isStudent =
if age < 5 then 0
elif age < 12 then 50
elif age >= 60 then 60
elif isStudent then 70
else 100
let priceWithMatch age isStudent =
match age, isStudent with
| a, _ when a < 5 -> 0
| a, _ when a < 12 -> 50
| a, _ when a >= 60 -> 60
| _, true -> 70
| _, false -> 100
printfn "%-4s %-8s %4s %6s" "Age" "Student" "ifs" "match"
for age, student in [ 3, false; 9, false; 19, true; 35, false; 67, true ] do
printfn "%-4d %-8b %4d %6d" age student (priceWithIfs age student) (priceWithMatch age student) from session import fsi, run
# ticket.fsx prints both versions side by side; then a match with a rule missing, a rule out of order, and a match
# whose only rule for non-students has a guard.
run("""
dotnet fsi ticket.fsx
""")
fsi([
'let describe n =\n match n with\n | 0 -> "none"\n | 1 -> "one";;',
"try describe 2 with e -> e.GetType().Name;;",
'let describe2 n =\n match n with\n | _ -> "many"\n | 0 -> "none";;',
"let price2 age isStudent =\n match age, isStudent with\n | a, _ when a < 5 -> 0\n | _, true -> 70;;",
"#quit;;",
], args=["--nologo"]) Output
$ dotnet fsi ticket.fsx
Age Student ifs match
3 false 0 0
9 false 50 50
19 true 70 70
35 false 100 100
67 true 60 60
$ dotnet fsi --nologo
> let describe n =
- match n with
- | 0 -> "none"
- | 1 -> "one";;
match n with
----------^
stdin(2,11): warning FS0025: Incomplete pattern matches on this expression. For example, the value '2' may indicate a case not covered by the pattern(s).
val describe: n: int -> string
> try describe 2 with e -> e.GetType().Name;;
val it: string = "MatchFailureException"
> let describe2 n =
- match n with
- | _ -> "many"
- | 0 -> "none";;
| 0 -> "none";;
------^
stdin(9,7): warning FS0026: This rule will never be matched
val describe2: n: int -> string
> let price2 age isStudent =
- match age, isStudent with
- | a, _ when a < 5 -> 0
- | _, true -> 70;;
match age, isStudent with
----------^^^^^^^^^^^^^^
stdin(11,11): warning FS0025: Incomplete pattern matches on this expression. For example, the value '(_,false)' may indicate a case not covered by the pattern(s). However, a pattern rule with a 'when' clause might successfully match this value.
val price2: age: int -> isStudent: bool -> int
> #quit;;
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 match_session.py
Both columns agree on all five rows. The match reads its rules from the top, as the match expressions reference
describes: it compares the value with each pattern in turn and returns the result of the first that fits. A
guard: The when clause of a match rule, such as | a when a < 5 -> 0: a bool expression that must also be true for the rule to fit, evaluated only after the pattern has fitted., the when part, adds a condition that must hold too, and it is evaluated only when the
pattern has fitted. Step through a student of 19:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- One value, a tuple. match age, isStudent with matches one value, the tuple (19, true), against the rules from the top.
- Pattern fits, guard fails. The pattern a, _ fits any tuple and binds a to 19, but its guard, when a < 5, is false, so the rule does not fit and the next is tried.
- Two more guards. a < 12 and a >= 60 are false for 19 as well, so the second and third rules are skipped the same way.
- The rule that fits. _, true fits, because the second element is true and the pattern has no guard. The match takes its value, 70, which the table printed in both columns.
- Every case is covered. The last rule, _, false, catches every tuple left, so there is no FS0025. Without a rule for false, the compiler warned FS0025 for (_,false), adding that a rule with a when clause might match it.
The compiler checks the rules. describe has rules for 0 and 1 only, and warning FS0025 gave 2 as a value no rule
covers; when describe 2 ran anyway, it raised MatchFailureException. describe2 puts the wildcard _ first, so the
rule for 0 can never be reached, and warning FS0026 says so. price2 shows how guards count: the compiler cannot know
whether a when clause holds, so it warned FS0025 for (_,false) while noting that a guarded rule might match it.
End with the rule that catches the rest
Keep the patterns that need guards first and finish with rules that need none, such as _, false in
priceWithMatch. A _ at the end silences FS0025, so use it only when every remaining value really gets the same
answer.
Checkpoint
Check: match rules, guards and their warnings
Loops run for their effect and have the type unit
F# has four loops, and each is an expression of type unit: it runs its body for its effect and produces (). These
entries use all four:
from session import fsi
# The loops of F#, each an expression of type unit: for...in over a list and a range, for...to, for...downto and
# while; then a loop body that produces a value, and break.
fsi([
"let names = [ \"Asha\"; \"Ravi\"; \"Meera\" ];;",
'for name in names do printfn "Hello %s" name;;',
'for i in 1 .. 3 do printfn "pass %d" i;;',
'for i = 1 to 3 do printfn "up %d" i;;',
'for i = 3 downto 1 do printfn "%d..." i;;',
"let mutable tries = 0;;",
"while tries < 3 do tries <- tries + 1;;",
"tries;;",
"let r = for i in 1 .. 2 do ();;",
"for i in 1 .. 3 do i * 2;;",
'for i in 1 .. 5 do\n if i = 3 then break\n printfn "%d" i;;',
"#quit;;",
], args=["--nologo"]) Output
$ dotnet fsi --nologo
> let names = [ "Asha"; "Ravi"; "Meera" ];;
val names: string list = ["Asha"; "Ravi"; "Meera"]
> for name in names do printfn "Hello %s" name;;
Hello Asha
Hello Ravi
Hello Meera
val it: unit = ()
> for i in 1 .. 3 do printfn "pass %d" i;;
pass 1
pass 2
pass 3
val it: unit = ()
> for i = 1 to 3 do printfn "up %d" i;;
up 1
up 2
up 3
val it: unit = ()
> for i = 3 downto 1 do printfn "%d..." i;;
3...
2...
1...
val it: unit = ()
> let mutable tries = 0;;
val mutable tries: int = 0
> while tries < 3 do tries <- tries + 1;;
val it: unit = ()
> tries;;
val it: int = 3
> let r = for i in 1 .. 2 do ();;
val r: unit = ()
> for i in 1 .. 3 do i * 2;;
for i in 1 .. 3 do i * 2;;
-------------------^^^^^
stdin(10,20): warning FS0020: The result of this expression has type 'int32' and is implicitly ignored. Consider using 'ignore' to discard this value explicitly, e.g. 'expr |> ignore', or 'let' to bind the result to a name, e.g. 'let result = expr'.
val it: unit = ()
> for i in 1 .. 5 do
- if i = 3 then break
- printfn "%d" i;;
if i = 3 then break
------------------^^^^^
stdin(12,19): warning FS0046: The identifier 'break' is reserved for future use by F#
if i = 3 then break
------------------^^^^^
stdin(12,19): error FS0039: The value or constructor 'break' is not defined.
> #quit;;
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 loops_session.py
for name in names takes each item of a list, as C#’s foreach does, and works on any sequence, array or range.
for i in 1 .. 3 loops over a range, for i = 1 to 3 counts up without one, and for i = 3 downto 1 counts down;
both include their ends. while tries < 3 repeats while its test is true, so something the test reads must change
in the body, here a let mutable value. let r = for … do () showed the loop’s own value: val r: unit = ().
A loop body that produces a value earns the same warning as any discarded result: for i in 1 .. 3 do i * 2 gave
FS0020 for an int32, because the loop throws each product away. If you want the doubled values, ask for a list with
[ for i in 1 .. 3 -> i * 2 ] or List.map, which a later lesson covers. The four loops at a glance:
| Loop | Example | Note |
|---|---|---|
for … in … do |
for name in names do … |
any list, array, sequence or range |
for … = … to … do |
for i = 1 to 3 do … |
counts up, both ends included |
for … = … downto … do |
for i = 3 downto 1 do … |
counts down |
while … do |
while tries < 3 do … |
needs a value that changes |
Writing break or continue
Wrong: if i = 3 then break. F# warned FS0046, that break is reserved for future use, and stopped with error
FS0039: it is not defined. There is no continue either. Right: decide with the loop’s condition, or replace the
loop with a search, as the next section does.
Checkpoint
Check: for and while loops
F# has no break: search with recursion or List.tryFind
The usual reason for break is a search: stop at the first item that fits. Here is one search written three ways, the
first train of the day that still has a seat:
// Open seats on each train of the day, in order of departure. Which is the first train with a seat?
let trains = [ "T101", 0; "T102", 0; "T205", 3; "T309", 12 ]
// 1. A while loop with a flag, the shape a C# loop with break takes in F#.
let firstWithWhile (all: (string * int) list) =
let mutable i = 0
let mutable found = None
while found.IsNone && i < all.Length do
let number, seats = all[i]
if seats > 0 then found <- Some number
i <- i + 1
found
// 2. List.tryFind returns the first element for which the test is true, or None.
let firstWithTryFind all =
all |> List.tryFind (fun (_, seats) -> seats > 0) |> Option.map fst
// 3. A recursive function: each call looks at one train and decides whether to go on.
let rec firstWithSeats all =
match all with
| [] -> None
| (number, seats) :: _ when seats > 0 -> Some number
| _ :: rest -> firstWithSeats rest
printfn "while with a flag: %A" (firstWithWhile trains)
printfn "List.tryFind: %A" (firstWithTryFind trains)
printfn "recursion: %A" (firstWithSeats trains)
printfn "all trains full: %A" (firstWithSeats [ "T101", 0; "T102", 0 ]) from session import run
# The same search three ways, then on a day when every train is full.
run("""
dotnet fsi trains.fsx
""") Output
$ dotnet fsi trains.fsx while with a flag: Some "T205" List.tryFind: Some "T205" recursion: Some "T205" all trains full: None
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 trains_session.py
All three found Some "T205", and the recursive one returned None when every train was full. The while version
needs two mutable values, an index and a flag, and the loop’s test must check both. List.tryFind says what is wanted
in one line: its documentation describes it as returning the first element for which the function returns true, or
None. The recursive function, a
A function that calls itself, declared with let rec. Each call handles one step of the work, such as one item of a list, and calls itself on what is left.
declared with let rec, looks at one train and either answers or calls itself on the rest, so stopping is simply not
calling itself again:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- The first train. firstWithSeats trains matches the list. Its head is T101 with 0 seats, so the guard seats > 0 is false and the last rule calls firstWithSeats on the rest.
- The second train. T102 has 0 seats as well, so the function calls itself once more, on the two trains that are left.
- The answer. T205 has 3 seats, so the guard is true and the call returns Some "T205" without calling itself again. This is the job break does in a C# loop.
- The rest is never visited. T309 is never looked at. The answer passes back through the earlier calls unchanged and becomes the value of the first call.
- When every train is full. On a day with only full trains the list runs out, the [] rule matches, and the result is None: the run printed None, with no exception and no flag.
Use the List function when one exists for the job (List.tryFind, List.exists, List.takeWhile), recursion when the
step is your own, and a while loop with a flag when the code must stay close to an imperative original.
Key idea
A search stops by returning: List.tryFind returns at the first match, and a recursive function stops by not calling itself again, so neither needs break.
Checkpoint
Check: Searching without break
Ranges build lists, arrays and sequences
A range is written start .. finish, or start .. step .. finish, inside the brackets of the collection you want:
from session import fsi
# Range expressions in lists, arrays and sequences: a step, a step down, an empty range, and the bare { } form that
# F# 10 deprecates.
fsi([
"[ 1 .. 3 ];;",
"[| 0 .. 2 .. 10 |];;",
"[ 10 .. -3 .. 1 ];;",
"[ 5 .. 1 ];;",
"{ 1..3 };;",
"seq { 1..3 };;",
"seq { 1..3 } |> Seq.sum;;",
"#quit;;",
], args=["--nologo"]) Output
$ dotnet fsi --nologo
> [ 1 .. 3 ];;
val it: int list = [1; 2; 3]
> [| 0 .. 2 .. 10 |];;
val it: int array = [|0; 2; 4; 6; 8; 10|]
> [ 10 .. -3 .. 1 ];;
val it: int list = [10; 7; 4; 1]
> [ 5 .. 1 ];;
val it: int list = []
> { 1..3 };;
{ 1..3 };;
^^^^^^^^
stdin(5,1): warning FS3873: This construct is deprecated. Sequence expressions should be of the form 'seq { ... }'
val it: int seq = seq [1; 2; 3]
> seq { 1..3 };;
val it: int seq = seq [1; 2; 3]
> seq { 1..3 } |> Seq.sum;;
val it: int = 6
> #quit;;
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 ranges_session.py
[ 1 .. 3 ] is a list, [| 0 .. 2 .. 10 |] an array that steps by 2, and [ 10 .. -3 .. 1 ] counts down by 3, as
the for…in reference describes for a negative step. A range expression: start .. finish, or start .. step .. finish, inside [ ], [| |] or seq { }, which lists every value from start to finish: [ 1 .. 3 ] is [1; 2; 3], and a range whose finish is below its start is empty. whose
finish comes before its start is simply empty: [ 5 .. 1 ] gave [], not an error.
The braces alone are on their way out. { 1..3 } still produced a sequence, but with warning FS3873: this construct is
deprecated, and sequence expressions should be of the form seq { ... }. The What’s new page for F# 10 calls this a
warning for now, and says that a future version may make it an error; seq { 1..3 } is the form to write. A sequence
is lazy, computing its items only when something asks for them, as Seq.sum did here; the sequences lesson of this
track covers what that means. FS3873 is the newest of several changes around loops, ranges and lists that you will
meet when you read code written for older or newer versions of F#:
- F# 6
expr[idx] for indexing new syntax
F# 6 allowed list[i], array[i] and str[i] for indexing, as trains.fsx uses, where older code writes list.[i]. The older form still works, and the What's new page recommends the new one.
- F# 6
Fewer indentation rules
F# 6 removed a number of indentation limitations. A list may now open with [ at the end of a line and close with ] at the start of one, as a parenthesised block already could.
- F# 8
while! in computation expressions new syntax
while! loops on a condition that has to be awaited, inside a computation expression such as task { }. The task lesson of this track uses it.
- F# 8
try/with inside seq, [ ] and [| |]
Handlers for exceptions became allowed inside sequence, list and array expressions, so a generated collection can recover from a failing item.
- F# 10
Bare { 1..10 } is deprecated deprecation
A range in braces with no seq in front now gives warning FS3873, and the What's new page says a future version may make it an error. Write seq { 1..10 } instead.
The timeline has 5 events, from F# 6 to F# 10.
Checkpoint
Check: Ranges and seq
Practise: pay an amount in notes, as one expression
dispense pays an amount with notes of an illustrative set, 500, 200 and 100, largest first, or says why it cannot.
Every branch of its if and every rule of its match returns a value, so it answers for every amount without an
exception:
// Pays an amount with notes of an illustrative set, largest first, or says why it cannot. Every branch is an
// expression, so dispense returns a value for every amount and raises no exception.
let notes = [ 500; 200; 100 ]
let dispense (amount: int) : Result<(int * int) list, string> =
if amount <= 0 then
Error "the amount must be more than 0"
elif amount % 100 <> 0 then
Error "the amount must be a multiple of 100"
else
let rec pay remaining notesLeft =
match notesLeft with
| [] -> []
| note :: rest ->
let count = remaining / note
let taken = if count > 0 then [ (note, count) ] else []
taken @ pay (remaining - count * note) rest
Ok(pay amount notes)
for amount in [ 3700; 600; 100; 0; -500; 250; 2_000_000 ] do
match dispense amount with
| Ok counts -> printfn "%9d -> %A" amount counts
| Error why -> printfn "%9d -> refused: %s" amount why from session import run
# dispense on amounts that work, the edge cases 0 and a negative amount, a non-multiple of 100 and a large amount.
run("""
dotnet fsi notes.fsx
""") Output
$ dotnet fsi notes.fsx
3700 -> [(500, 7); (200, 1)]
600 -> [(500, 1); (100, 1)]
100 -> [(100, 1)]
0 -> refused: the amount must be more than 0
-500 -> refused: the amount must be more than 0
250 -> refused: the amount must be a multiple of 100
2000000 -> [(500, 4000)]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 notes_session.py
Result holds either Ok with the notes or Error with a message; the lesson on errors and validation of this track
covers it in depth. 3,700 was paid as seven 500s and one 200, 0 and -500 were refused by the first test, 250 by the
second, and two million came out as 4,000 notes of 500. The inner pay is recursive: each call takes as many notes
of one value as fit, then calls itself with the amount that remains and the smaller notes.
Add a limit to the machine
Make dispense refuse amounts above 20,000 with the message “the amount is above the limit”, as a new elif before
the else, and run dotnet fsi notes.fsx: the line for 2,000,000 now says refused, and the others are unchanged.
Now check the five ideas of the lesson. The practice keeps track of which ones your answers show you know, asks about the weakest first, and explains every choice once you pick it:
Practice that adapts to you
Practise this lesson
10 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher to 2–5 years): why must every branch of an F# if/then/else have the same type, and what happens without an else?
In F#, if…then…else is an expression: it produces the value of the branch that runs, so the compiler must know one
type for the whole if, and every branch must have it. if age >= 60 then "senior" else 0 is error FS0001, because
the first branch makes it a string. Without an else the if has no value when the test is false, so its type is
unit and the then branch must be unit as well: if ok then printfn "x" compiles, while
let label = if ok then "senior" is error FS0001, whose message asks for an else branch of type string. The same
rule makes match safe: every rule returns the same type, and the compiler warns FS0025 when a value has no rule.
Two harder questions follow, about porting a loop with break and about a wildcard that hid a new case:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
You are porting a C# loop over sensor readings that uses continue to skip invalid readings and break to stop at the first reading above a threshold. The first F# draft is a while loop with two mutable flags. How would you write it idiomatically, and how do you show that it behaves the same?
-
Mid to senior
A discount function matches on a customer category and ends with | _ -> standardRate. A category added last month has been priced at the standard rate ever since, and no warning appeared. Why was the compiler silent, and how would you write such matches so that it warns next time?
Key takeaways
if…then…elsehas the value of the branch that runs, so every branch has one type; withoutelsetheifisunit, and a branch with a value is error FS0001.matchtakes the first rule that fits, guards add conditions, FS0025 reports a value no rule covers and FS0026 a rule that can never be reached.for…in,for…to,for…downtoandwhileare expressions of typeunit; F# has nobreakorcontinue, and a body that produces a value earns FS0020.- A search stops by returning: use
List.tryFindand its relatives, or a recursive function, instead of a loop with a flag. - Ranges such as
[ 1 .. 3 ]and[| 0 .. 2 .. 10 |]build collections; a bare{ 1..3 }warns FS3873 in F# 10, so writeseq { 1..3 }.
Terms in this lesson
- guard
- The when clause of a match rule, such as | a when a < 5 -> 0: a bool expression that must also be true for the rule to fit, evaluated only after the pattern has fitted.
- range expression
- start .. finish, or start .. step .. finish, inside [ ], [| |] or seq { }, which lists every value from start to finish: [ 1 .. 3 ] is [1; 2; 3], and a range whose finish is below its start is empty.
References
- Conditional expressions: if...then...else (Microsoft Learn)
- Match expressions (Microsoft Learn)
- Loops: for...in expression (Microsoft Learn)
- Loops: for...to expression (Microsoft Learn)
- Loops: while...do expression (Microsoft Learn)
- List module (tryFind) (FSharp.Core documentation)
- What's new in F# 10 (Microsoft Learn)
- F# RFC FS-1033: deprecate places where seq can be omitted (F# Software Foundation (fslang-design))
- What's new in F# 6 (Microsoft Learn)
- Announcing F# 8 (Microsoft .NET Blog)
Report a problem with this lesson
Kept only in this browser. Your Learn progress