Fsharp Cheatsheet
Fsharp Cheatsheet
Contents
• Comments
• Strings
• Basic Types and Literals
• Functions
• Pattern Matching
• Collections
• Tuples and Records
• Discriminated Unions
• Statically Resolved Type Parameters
• Exceptions
• Classes and Inheritance
• Interfaces and Object Expressions
• Active Patterns
• Code Organization
• Compiler Directives
Comments
Block comments are placed between (* and *). Line comments start from //
and continue until the end of the line.
(* This is block comment *)
Strings
F# string type is an alias for System.String type.
// Create a string using string concatenation
let hello = "Hello" + " World"
1
Use verbatim strings preceded by @ symbol to avoid escaping control characters
(except escaping " by "").
let verbatimXml = @"<book title=""Paradise Lost"">"
We don’t even have to escape " with triple-quoted strings.
let tripleXml = """<book title="Paradise Lost">"""
Backslash strings indent string contents by stripping leading spaces.
let poem =
"The lesser world was daubed\n\
By a colorist of modest skill\n\
A master limned you in the finest inks\n\
And with a fresh-cut quill."
String Slicing is supported by using [start..end] syntax.
let str = "Hello World"
let firstWord = str[0..4] // "Hello"
let lastWord = str[6..] // "World"
String Interpolation is supported by prefixing the string with $ symbol. All of
these will output "Hello" \ World!:
let expr = "Hello"
printfn " \"%s\" \\ World! " expr
printfn $" \"{expr}\" \\ World! "
printfn $" \"%s{expr}\" \\ World! " // using a format specifier
printfn $@" ""{expr}"" \ World! "
printfn $@" ""%s{expr}"" \ World! "
See Strings (MS Learn) for more on escape characters, byte arrays, and format
specifiers.
2
let bigInt = 9999999999999I // System.Numerics.BigInteger
Functions
The let keyword also defines named functions.
let negate x = x * -1
let square x = x * x
let print x = printfn "The number is: %d" x
3
Composition operator >> is used to compose functions:
let squareNegateThenPrint' =
square >> negate >> print
Recursive Functions
The rec keyword is used together with the let keyword to define a recursive
function:
let rec fact x =
if x < 1 then 1
else x * fact (x - 1)
Mutually recursive functions (those functions which call each other) are indicated
by and keyword:
let rec even x =
if x = 0 then true
else odd (x - 1)
and odd x =
if x = 0 then false
else even (x - 1)
Pattern Matching
Pattern matching is often facilitated through match keyword.
let rec fib n =
match n with
| 0 -> 0
| 1 -> 1
| _ -> fib (n - 1) + fib (n - 2)
In order to match sophisticated inputs, one can use when to create filters or
guards on patterns:
let sign x =
match x with
| 0 -> 0
| x when x < 0 -> -1
| x -> 1
Pattern matching can be done directly on arguments:
let fst' (x, _) = x
or implicitly via function keyword:
/// Similar to `fib`; using `function` for pattern matching
4
let rec fib' = function
| 0 -> 0
| 1 -> 1
| n -> fib' (n - 1) + fib' (n - 2)
For more complete reference visit Pattern Matching (MS Learn).
Collections
Lists
A list is an immutable collection of elements of the same type.
// Lists use square brackets and `;` delimiter
let list1 = [ "a"; "b" ]
// :: is prepending
let list2 = "c" :: list1
// @ is concat
let list3 = list1 @ list2
Arrays
Arrays are fixed-size, zero-based, mutable collections of consecutive data elements.
// Arrays use square brackets with bar
let array1 = [| "a"; "b" |]
// Indexed access using dot
let first = array1.[0]
Sequences
A sequence is a logical series of elements of the same type. Individual sequence
elements are computed only as required, so a sequence can provide better
performance than a list in situations in which not all the elements are used.
// Sequences can use yield and contain subsequences
let seq1 =
seq {
// "yield" adds one element
yield 1
yield 2
5
yield! [5..10]
}
6
Tuples and Records
Tuple
A tuple is a grouping of unnamed but ordered values, possibly of different types:
// Tuple construction
let x = (1, "Hello")
// Triple
let y = ("one", "two", "three")
Record
Records represent simple aggregates of named values, optionally with members:
// Declare a record type
type Person = { Name : string; Age : int }
7
Discriminated Unions
Discriminated unions (DU) provide support for values that can be one of a
number of named cases, each possibly with different values and types.
type Tree<'T> =
| Node of Tree<'T> * 'T * Tree<'T>
| Leaf
// Create a DU value
let orderId = Order "12"
8
let inline getId<'t when 't : (member Id: string)> (x: 't) = x.Id
Exceptions
Try..With
An illustrative example with: custom F# exception creation, all exception
aliases, raise() usage, and an exhaustive demonstration of the exception handler
patterns:
open System
exception MyException of int * string // (1)
let guard = true
try
failwith "Message" // throws a System.Exception (aka exn)
nullArg "ArgumentName" // throws a System.ArgumentNullException
invalidArg "ArgumentName" "Message" // throws a System.ArgumentException
invalidOp "Message" // throws a System.InvalidOperation
true // (3)
with
| :? ArgumentNullException -> printfn "NullException"; false // (3)
| :? ArgumentException as ex -> printfn $"{ex.Message}"; false // (4)
| :? InvalidOperationException as ex when guard -> printfn $"{ex.Message}"; reraise() // (5,
| MyException(num, str) when guard -> printfn $"{num}, {str}"; false // (5)
| MyException(num, str) -> printfn $"{num}, {str}"; reraise() // (6)
| ex when guard -> printfn $"{ex.Message}"; false
| ex -> printfn $"{ex.Message}"; false
(1) define your own F# exception types with exception, a new type that will
inherit from System.Exception;
(2) use raise() to throw an F# or .NET exception;
(3) the entire try..with expression must evaluate to the same type,
in this example: bool; (4)ArgumentNullException inherits from
ArgumentException, so ArgumentException must follow after;
(4) support for when guards;
9
(5) use reraise() to re-throw an exception; works with both .NET and F#
exceptions
The difference between F# and .NET exceptions is how they are created and
how they can be handled.
Try..Finally
The try..finally expression enables you to execute clean-up code even if a
block of code throws an exception. Here’s an example that also defines custom
exceptions.
exception InnerError of string
exception OuterError of string
let handleErrors x y =
try
try
if x = y then raise (InnerError("inner"))
else raise (OuterError("outer"))
with
| InnerError str -> printfn "Error1 %s" str
finally
printfn "Always print this."
Note that finally does not follow with. try..with and try..finally are
separate expressions.
type Dog() =
10
inherit Animal()
member _.Run() =
base.Rest()
Upcasting is denoted by :> operator.
let dog = Dog()
let animal = dog :> Animal
Dynamic downcasting (:?>) might throw an InvalidCastException if the cast
doesn’t succeed at runtime.
let shouldBeADog = animal :?> Dog
type Vector'(x, y) =
interface IVector with
member __.Scale(s) =
Vector'(x * s, y * s) :> IVector
member _.X = x
member _.Y = y
Another way of implementing interfaces is to use object expressions.
type ICustomer =
abstract Name : string
abstract Age : int
Active Patterns
Single-case active patterns
// Basic
let (|EmailDomain|) email =
let match' = Regex.Match(email, "@(.*)$")
if match'.Success
then match'.Groups[1].ToString()
else ""
let (EmailDomain emailDomain) = "[email protected]" // emailDomain = 'aretuza.org'
11
// As Parameters
open System.Numerics
let (|Real|) (x: Complex) =
(x.Real, x.Imaginary)
let addReal (Real (real1, _)) (Real (real2, _)) = // conversion done in the parameters
real1 + real2
let addRealOut = addReal Complex.ImaginaryOne Complex.ImaginaryOne
// Parameterized
let (|Default|) onNone value =
match value with
| None -> onNone
| Some e -> e
let (Default "random citizen" name) = None // name = "random citizen"
let (Default "random citizen" name) = Some "Steve" // name = "Steve"
Single-case active patterns can be thought of as a simple way to convert data to
a new form.
let testNumber i =
match i with
| Even -> printfn "%d is even" i
| Odd -> printfn "%d is odd" i
12
Partial active patterns share the syntax of parameterized patterns but their
active recognizers accept only one argument.
Code Organization
Modules
Modules are key building blocks for grouping related code; they can contain
types, let bindings, or (nested) sub modules. Identifiers within modules can
be referenced using dot notation, or you can bring them into scope via the open
keyword. Illustrative-only example:
module Money =
type CardInfo =
{ number: string
expiration: int * int }
type Payment =
| Card of CardInfo
| Cash of int
module Functions =
let validCard (cardNumber: string) =
cardNumber.Length = 16 && (cardNumber[0], ['3';'4';'5';'6']) ||> List.contains
If there is only one module in a file, the module name can be declared at the top,
and all code constructs within the file will be included in the modules definition
(no indentation required).
module Functions // notice there is no '=' when at the top of a file
Namespaces
Namespaces are simply dotted names that prefix type and module declarations
to allow for hierarchical scoping. The first namespace directives must be placed
at the top of the file. Subsequent namespace directives either: (a) create a
sub-namespace; or (b) create a new namespace.
namespace MyNamespace
namespace MyNamespace.SubNamespace
13
A top-level module’s namespace can be specified via a dotted prefix:
module MyNamespace.SubNamespace.Functions
Accessibility Modifiers
F# supports public, private (limiting access to its containing type or module)
and internal (limiting access to its containing assembly). They can be applied
to module, let, member, type, new (MS Learn), and val (MS Learn).
14
With the exception of let bindings in a class type, everything defaults to
public.
Smart Constructors
Making a primary constructor (ctor) private or internal is a common con-
vention for ensuring value integrity; otherwise known as “making illegal states
unrepresentable” (YouTube:Effective ML).
Example of Single-case Discriminated Union with a private constructor that
constrains a quantity between 0 and 100:
type UnitQuantity =
private UnitQuantity of int
let validQty =
unitQtyOpt
|> Option.defaultValue UnitQuantity.zero
15
Recursive Reference
F#’s type inference and name resolution runs in file and line order. By default,
any forward references are considered errors. This default provides a single
benefit, which can be hard to appreciate initially: you never need to look beyond
the current file for a dependency. In general this also nudges toward more careful
design and organisation of codebases, which results in cleaner, maintainable
code. However, in rare cases forward referencing might be needed. To do this
we have rec for module and namespace; and and for type and let (Recursive
Functions) functions.
module rec CarModule
type Car =
{ make: string; model: string; hasGas: bool }
member self.Drive destination =
if not self.hasGas
then raise (OutOfGasException self)
else ...
type Person =
{ Name: string; Address: Address }
and Address =
{ Line1: string; Line2: string; Occupant: Person }
See Namespaces (MS Learn) and Modules (MS Learn) to learn more.
Compiler Directives
Load another F# source file into FSI.
#load "../lib/StringParsing.fs"
Reference a .NET assembly (/ symbol is recommended for Mono compatibility).
Reference a .NET assembly:
#r "../lib/FSharp.Markdown.dll"
Reference a nuget package
#r "nuget:Serilog.Sinks.Console" // latest production release
#r "nuget:FSharp.Data, 6.3.0" // specific version
#r "nuget:Equinox, *-*" // latest version, including `-alpha`, `-rc` version etc
Include a directory in assembly search paths.
#I "../lib"
16
#r "FSharp.Markdown.dll"
Other important directives are conditional execution in FSI (INTERACTIVE) and
querying current directory (__SOURCE_DIRECTORY__).
#if INTERACTIVE
let path = __SOURCE_DIRECTORY__ + "../lib"
#else
let path = "../../../lib"
#endif
17