In F# almost everything is an expression with a value: if, match, try and even a block of statements. A block is a list of expressions one under the other; every one but the last must have type unit (a warning otherwise), and the last gives the value.
Bindings
let x = 10 // an immutable value
let mutable count = 0 // a mutable one
count <- count + 1 // assignment uses <-
let f a b = a + b // a function
let a, b = 1, 2 // a pattern on the left
let x = 5 in x + 1 // verbose form with in
let result =
let y = x * 2 // local bindings in a block
let z = y + 1
z * z // the value of the block
let r = ref 0 // a reference cell
r.Value <- r.Value + 1
[<Literal>]
let Limit = 100 // a constant, usable in patterns
do printfn "starting" // do: run an expression for its effect
mutable and ref opt out. let at the top of a module defines a module member.A later let of the same name shadows the earlier one inside a block (but not at the top level of a module, where names must be unique). A mutable local cannot be captured by a closure; use a ref cell for that. use is let for a value that is disposed at the end of its scope (see below). let rec allows recursion, with and for mutual recursion.
Conditionals and loops
let sign n =
if n > 0 then "positive"
elif n < 0 then "negative"
else "zero"
if debug then printfn "x = %d" x // no else: the then part must be unit
for i in 1 .. 10 do printf "%d " i // over a range
for i = 0 to 9 do printf "%d " i // an integer loop, to
for i = 9 downto 0 do printf "%d " i // counting down
for x in [ "a"; "b" ] do printfn "%s" x // over any sequence
for (k, v) in Map.toList m do printfn "%s=%d" k v // with a pattern
let mutable n = 0
while n < 3 do
n <- n + 1
let evens = [ for i in 0 .. 2 .. 10 -> i ] // 0, 2, 4, 6, 8, 10
let squares = [| for i in 1 .. 5 do yield i * i |]
if is an expression; the loops give unit; a for inside [ ], [| |] or seq { } collects values.- There is no
break,continueorreturnin a loop (the first two are reserved words); use recursion,whilewith a flag,Seq.takeWhileor a function that finds the first match. - A range is
a .. bora .. step .. b; it works for integers, floats, characters and any type with the needed operators.1..5and1 .. 5are the same. match … withis a multi-way branch (see pattern matching).
Exceptions
exception NotFound of string // an F# exception type
let find key =
try
if key = "" then invalidArg "key" "empty"
elif key = "?" then raise (NotFound key)
else failwith "boom"
with
| NotFound k -> printfn "no %s" k
| :? System.ArgumentException as e -> printfn "%s" e.Message
| e -> reraise () // rethrow keeping the stack
let cleanup () =
try
printfn "work"
finally
printfn "always" // try ... finally cannot also have with
let r = try int "x" with _ -> 0 // try is an expression
try … with matches the exception with patterns; try … finally is a separate form.raise e throws; failwith "message" throws Failure; failwithf, invalidArg, invalidOp and nullArg throw the usual .NET ones; reraise () may only be used inside a with handler; exception Name of fields declares a new exception with pattern-matchable fields. assert expr raises in debug builds (DEBUG defined).
Operators and conversions
- Arithmetic
+ - * / % **, comparison= <> < > <= >=(structural), boolean&& || not, bitwise&&& ||| ^^^ ~~~ <<< >>>, string and list concatenation+and@, cons::, pipelines|>and<|, composition>>and<<. There is no implicit numeric conversion betweenintandfloat: writefloat n(F# 6 added implicit widening of some integers, with optional warnings). - Integer arithmetic is unchecked;
Checked.(+)and theCheckedmodule raise on overflow. - Casts:
x :> T(up, checked at compile time),x :?> T(down, checked at run time),x :? T(a test),upcastanddowncast(inferred target),boxandunbox,tryUnbox. typeof<T>,typedefof<T>,sizeof<T>,nameof x,Unchecked.defaultof<T>,__SOURCE_DIRECTORY__.- Indexing
a.[i]ora[i](F# 6), slicesa.[1..3],a.[..2],a.[2..], reverse indexa[^1](preview in the compiler’s table), 2Da.[i, j]. Member accessx.Name, method callx.M(args), optional-style chaining usesOption.map._.Name(F# 8) is shorthand forfun x -> x.Name. lazy exprdelays an expression;.Force()or.Valueruns it once.fixedpins an array or string and gives a native pointer.
Resources
let readAll path =
use reader = new System.IO.StreamReader(path) // disposed at the end of the block
reader.ReadToEnd()
let inline using' (r: #System.IDisposable) f = try f r finally r.Dispose()
using (new System.IO.StringReader "x") (fun r -> r.ReadToEnd()) |> ignore
use disposes at the end of its scope; using is the function form.Verbose syntax
Light syntax is shorthand for a verbose form with explicit tokens. The verbose forms still work: begin … end and ( … ) group expressions, let x = e1 in e2 separates a binding from its body, for … do … done and while … do … done close a loop, and ; separates expressions on one line (a; b). A ;; ends an interactive entry in F# Interactive. The in keyword is optional in light syntax; one is needed when a let is written on one line with its body.
Not described
Known, not yet covered
- Reference: the evaluation rules (order of evaluation, the value restriction, byref arguments, tail calls and the
.tailprefix, the exact rules for implicit conversions of F# 6), the complete operator list ofOperators, checked arithmetic, and object construction expressions with property setters (Foo(Name = "x")) are only named; computation-expression forms are on their own page. - Reference: the documentation pages for if/then/else, loops, match and exception handling were checked by their headings and keyword tables only; the examples were not compiled. The reverse index
^1is listed in the compiler’s table as a preview feature (FromEndSlicing), so it may not work in a released compiler.