diff --git a/.gitignore b/.gitignore
index 8428df7..a3e3f5f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,6 +44,9 @@ local.properties
## Visual Studio
#################
+# Visual Studio cache/options directory
+.vs/
+
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md
index dd58085..2a46f8e 100644
--- a/docs/fsharp-cheatsheet.md
+++ b/docs/fsharp-cheatsheet.md
@@ -6,30 +6,30 @@ The Microsoft [F# Documentation](https://learn.microsoft.com/en-us/dotnet/fsharp
Contents
--------
-[Comments](#Comments)
-[Strings](#Strings)
-[Basic Types and Literals](#BasicTypesAndLiterals)
-[Functions](#Functions)
-[Pattern Matching](#PatternMatching)
-[Collections](#Collections)
-[Tuples and Records](#TuplesAndRecords)
-[Discriminated Unions](#DiscriminatedUnions)
-[Exceptions](#Exceptions)
-[Classes and Inheritance](#ClassesAndInheritance)
-[Interfaces and Object Expressions](#InterfacesAndObjectExpressions)
-[Active Patterns](#ActivePatterns)
+[Comments](#Comments)
+[Strings](#Strings)
+[Basic Types and Literals](#BasicTypesAndLiterals)
+[Functions](#Functions)
+[Pattern Matching](#PatternMatching)
+[Collections](#Collections)
+[Tuples and Records](#TuplesAndRecords)
+[Discriminated Unions](#DiscriminatedUnions)
+[Exceptions](#Exceptions)
+[Classes and Inheritance](#ClassesAndInheritance)
+[Interfaces and Object Expressions](#InterfacesAndObjectExpressions)
+[Active Patterns](#ActivePatterns)
[Compiler Directives](#CompilerDirectives)
Comments
--------
Block comments are placed between `(*` and `*)`. Line comments start from `//` and continue until the end of the line.
- (* This is block comment *)
+ (* This is block comment *)
// And this is line comment
-
-XML doc comments come after `///` allowing us to use XML tags to generate documentation.
-
+
+XML doc comments come after `///` allowing us to use XML tags to generate documentation.
+
/// The `let` keyword defines an (immutable) value
let result = 1 + 1 = 2
@@ -50,7 +50,7 @@ We don't even have to escape `"` with *triple-quoted strings*.
*Backslash strings* indent string contents by stripping leading spaces.
- let poem =
+ let poem =
"The lesser world was daubed\n\
By a colorist of modest skill\n\
A master limned you in the finest inks\n\
@@ -70,10 +70,10 @@ Other common examples are `F` or `f` for 32-bit floating-point numbers, `M` or `
let s, f, d, bi = 4.14F, 4.14, 0.7833M, 9999I
- // [fsi:val s : float32 = 4.14f]
- // [fsi:val f : float = 4.14]
- // [fsi:val d : decimal = 0.7833M]
- // [fsi:val bi : System.Numerics.BigInteger = 9999]
+ // [fsi:val s : float32 = 4.14f]
+ // [fsi:val f : float = 4.14]
+ // [fsi:val d : decimal = 0.7833M]
+ // [fsi:val bi : System.Numerics.BigInteger = 9999]
See [Literals (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/literals) for complete reference.
@@ -81,77 +81,77 @@ See [Literals (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/langua
---------
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
+ let negate x = x * -1
+ let square x = x * x
+ let print x = printfn "The number is: %d" x
- let squareNegateThenPrint x =
- print (negate (square x))
+ let squareNegateThenPrint x =
+ print (negate (square x))
### Pipe and composition operators
Pipe operator `|>` is used to chain functions and arguments together. Double-backtick identifiers are handy to improve readability especially in unit testing:
- let ``square, negate, then print`` x =
- x |> square |> negate |> print
+ let ``square, negate, then print`` x =
+ x |> square |> negate |> print
-This operator is essential in assisting the F# type checker by providing type information before use:
+This operator can assist the F# type checker by providing type information before use:
- let sumOfLengths (xs : string []) =
- xs
- |> Array.map (fun s -> s.Length)
- |> Array.sum
+ let sumOfLengths (xs : string []) =
+ xs
+ |> Array.map (fun s -> s.Length)
+ |> Array.sum
Composition operator `>>` is used to compose functions:
- let squareNegateThenPrint' =
- square >> negate >> print
+ 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)
+ 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)
+ 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)
+ 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)
+ 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
+ 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
+ let fst' (x, _) = x
or implicitly via `function` keyword:
/// Similar to `fib`; using `function` for pattern matching
- let rec fib' = function
- | 0 -> 0
- | 1 -> 1
- | n -> fib' (n - 1) + fib' (n - 2)
+ let rec fib' = function
+ | 0 -> 0
+ | 1 -> 1
+ | n -> fib' (n - 1) + fib' (n - 2)
For more complete reference visit [Pattern Matching (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/pattern-matching).
@@ -165,8 +165,8 @@ A *list* is an immutable collection of elements of the same type.
let list1 = [ "a"; "b" ]
// :: is prepending
let list2 = "c" :: list1
- // @ is concat
- let list3 = list1 @ list2
+ // @ is concat
+ let list3 = list1 @ list2
// Recursion on list using (::) operator
let rec sum list =
@@ -177,24 +177,24 @@ A *list* is an immutable collection of elements of the same type.
### Arrays
*Arrays* are fixed-size, zero-based, mutable collections of consecutive data elements.
- // Arrays use square brackets with bar
+ // Arrays use square brackets with bar
let array1 = [| "a"; "b" |]
// Indexed access using dot
- let first = array1.[0]
-
+ 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
+ // Sequences can use yield and contain subsequences
let seq1 =
- seq {
- // "yield" adds one element
- yield 1
- yield 2
-
- // "yield!" adds a whole subsequence
- yield! [5..10]
- }
+ seq {
+ // "yield" adds one element
+ yield 1
+ yield 2
+
+ // "yield!" adds a whole subsequence
+ yield! [5..10]
+ }
### Higher-order functions on collections
The same list `[ 1; 3; 5; 7; 9 ]` or array `[| 1; 3; 5; 7; 9 |]` can be generated in various ways.
@@ -215,8 +215,9 @@ Lists and arrays have comprehensive sets of higher-order functions for manipulat
- `fold` starts from the left of the list (or array) and `foldBack` goes in the opposite direction
- let xs' = Array.fold (fun str n ->
- sprintf "%s,%i" str n) "" [| 0..9 |]
+ let xs' =
+ Array.fold (fun str n ->
+ sprintf "%s,%i" str n) "" [| 0..9 |]
- `reduce` doesn't require an initial accumulator
@@ -224,19 +225,19 @@ Lists and arrays have comprehensive sets of higher-order functions for manipulat
- `map` transforms every element of the list (or array)
- let ys' = Array.map (fun x -> x * x) [| 0..9 |]
+ let ys' = Array.map (fun x -> x * x) [| 0..9 |]
- `iter`ate through a list and produce side effects
-
- let _ = List.iter (printfn "%i") [ 0..9 ]
+
+ let _ = List.iter (printfn "%i") [ 0..9 ]
All these operations are also available for sequences. The added benefits of sequences are laziness and uniform treatment of all collections implementing `IEnumerable<'T>`.
- let zs' =
- seq {
- for i in 0..9 do
- printfn "Adding %d" i
- yield i
+ let zs' =
+ seq {
+ for i in 0..9 do
+ printfn "Adding %d" i
+ yield i
}
Tuples and Records
@@ -247,19 +248,19 @@ A *tuple* is a grouping of unnamed but ordered values, possibly of different typ
let x = (1, "Hello")
// Triple
- let y = ("one", "two", "three")
+ let y = ("one", "two", "three")
// Tuple deconstruction / pattern
let (a', b') = x
The first and second elements of a tuple can be obtained using `fst`, `snd`, or pattern matching:
- let c' = fst (1, 2)
- let d' = snd (1, 2)
-
- let print' tuple =
- match tuple with
- | (a, b) -> printfn "Pair %A %A" a b
+ let c' = fst (1, 2)
+ let d' = snd (1, 2)
+
+ let print' tuple =
+ match tuple with
+ | (a, b) -> printfn "Pair %A %A" a b
*Records* represent simple aggregates of named values, optionally with members:
@@ -270,7 +271,7 @@ The first and second elements of a tuple can be obtained using `fst`, `snd`, or
let paul = { Name = "Paul"; Age = 28 }
// 'Copy and update' record expression
- let paulsTwin = { paul with Name = "Jim" }
+ let paulsTwin = { paul with Name = "Jim" }
Records can be augmented with properties and methods:
@@ -280,29 +281,29 @@ Records can be augmented with properties and methods:
Records are essentially sealed classes with extra topping: default immutability, structural equality, and pattern matching support.
let isPaul person =
- match person with
- | { Name = "Paul" } -> true
- | _ -> false
+ match person with
+ | { Name = "Paul" } -> true
+ | _ -> false
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
+ | Node of Tree<'T> * 'T * Tree<'T>
+ | Leaf
let rec depth = function
- | Node(l, _, r) -> 1 + max (depth l) (depth r)
- | Leaf -> 0
+ | Node(l, _, r) -> 1 + max (depth l) (depth r)
+ | Leaf -> 0
F# Core has built-in discriminated unions for error handling, e.g., [`option`](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/options) and [`Result`](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/results).
- let optionPatternMatch input =
- match input with
- | Some i -> printfn "input is an int=%d" i
- | None -> printfn "input is missing"
+ let optionPatternMatch input =
+ match input with
+ | Some i -> printfn "input is an int=%d" i
+ | None -> printfn "input is missing"
Single-case discriminated unions are often used to create type-safe abstractions with pattern matching support:
@@ -318,92 +319,92 @@ Single-case discriminated unions are often used to create type-safe abstractions
----------
The `failwith` function throws an exception of type `Exception`.
- let divideFailwith x y =
- if y = 0 then
- failwith "Divisor cannot be zero."
- else x / y
+ let divideFailwith x y =
+ if y = 0 then
+ failwith "Divisor cannot be zero."
+ else x / y
Exception handling is done via `try/with` expressions.
- let divide x y =
- try
- Some (x / y)
- with :? System.DivideByZeroException ->
- printfn "Division by zero!"
- None
-
+ let divide x y =
+ try
+ Some (x / y)
+ with :? System.DivideByZeroException ->
+ printfn "Division by zero!"
+ None
+
The `try/finally` expression enables you to execute clean-up code even if a block of code throws an exception. Here's an example which 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."
+ 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."
Classes and Inheritance
-----------------------
This example is a basic class with (1) local let bindings, (2) properties, (3) methods, and (4) static members.
- type Vector(x : float, y : float) =
- let mag = sqrt(x * x + y * y) // (1)
- member this.X = x // (2)
- member this.Y = y
- member this.Mag = mag
- member this.Scale(s) = // (3)
- Vector(x * s, y * s)
- static member (+) (a : Vector, b : Vector) = // (4)
- Vector(a.X + b.X, a.Y + b.Y)
+ type Vector(x : float, y : float) =
+ let mag = sqrt(x * x + y * y) // (1)
+ member _.X = x // (2)
+ member _.Y = y
+ member _.Mag = mag
+ member _.Scale(s) = // (3)
+ Vector(x * s, y * s)
+ static member (+) (a : Vector, b : Vector) = // (4)
+ Vector(a.X + b.X, a.Y + b.Y)
Call a base class from a derived one.
- type Animal() =
- member __.Rest() = ()
-
- type Dog() =
- inherit Animal()
- member __.Run() =
- base.Rest()
+ type Animal() =
+ member _.Rest() = ()
+
+ type Dog() =
+ inherit Animal()
+ member _.Run() =
+ base.Rest()
*Upcasting* is denoted by `:>` operator.
- let dog = Dog()
- let animal = dog :> Animal
+ 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
+ let shouldBeADog = animal :?> Dog
Interfaces and Object Expressions
---------------------------------
Declare `IVector` interface and implement it in `Vector'`.
- type IVector =
- abstract Scale : float -> IVector
-
- type Vector'(x, y) =
- interface IVector with
- member __.Scale(s) =
- Vector'(x * s, y * s) :> IVector
- member __.X = x
- member __.Y = y
+ type IVector =
+ abstract Scale : float -> IVector
+
+ 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
-
- let createCustomer name age =
- { new ICustomer with
- member __.Name = name
- member __.Age = age }
+ type ICustomer =
+ abstract Name : string
+ abstract Age : int
+
+ let createCustomer name age =
+ { new ICustomer with
+ member __.Name = name
+ member __.Age = age }
Active Patterns
---------------
@@ -413,12 +414,12 @@ Another way of implementing interfaces is to use *object expressions*.
let (|EmailDomain|) (email: string) =
let parts = email.Split '@'
parts[1]
- let (EmailDomain emailDomain) = "yennefer@aretuza.org" // emailDomain = 'aretuza.org'
+ let (EmailDomain emailDomain) = "yennefer@aretuza.org" // emailDomain = 'aretuza.org'
// As Parameters
let (|Real|) (x: System.Numerics.Complex) =
(x.Real, x.Imaginary)
- let addReal (Real aa) (Real bb) = // conversion done in the parameters
+ let addReal (Real aa) (Real bb) = // conversion done in the parameters
fst aa + fst bb
let addRealOut = addReal System.Numerics.Complex.ImaginaryOne System.Numerics.Complex.ImaginaryOne
@@ -427,38 +428,38 @@ Another way of implementing interfaces is to use *object expressions*.
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"
+ 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.
*Complete active patterns*:
- let (|Even|Odd|) i =
- if i % 2 = 0 then Even else Odd
-
- let testNumber i =
- match i with
- | Even -> printfn "%d is even" i
- | Odd -> printfn "%d is odd" i
+ let (|Even|Odd|) i =
+ if i % 2 = 0 then Even else Odd
+
+ let testNumber i =
+ match i with
+ | Even -> printfn "%d is even" i
+ | Odd -> printfn "%d is odd" i
let (|Phone|Email|) (s:string) =
if s.Contains '@' then Email $"Email: {s}" else Phone $"Phone: {s}"
- match "yennefer@aretuza.org" with // output: "Email: yennefer@aretuza.org"
+ match "yennefer@aretuza.org" with // output: "Email: yennefer@aretuza.org"
| Email email -> printfn $"{email}"
| Phone phone -> printfn $"{phone}"
*Partial active patterns*:
- let (|DivisibleBy|_|) by n =
- if n % by = 0 then Some DivisibleBy else None
-
- let fizzBuzz = function
- | DivisibleBy 3 & DivisibleBy 5 -> "FizzBuzz"
- | DivisibleBy 3 -> "Fizz"
- | DivisibleBy 5 -> "Buzz"
- | i -> string i
+ let (|DivisibleBy|_|) by n =
+ if n % by = 0 then Some DivisibleBy else None
+
+ let fizzBuzz = function
+ | DivisibleBy 3 & DivisibleBy 5 -> "FizzBuzz"
+ | DivisibleBy 3 -> "Fizz"
+ | DivisibleBy 5 -> "Buzz"
+ | i -> string i
*Partial active patterns* share the syntax of parameterized patterns but their active recognizers accept only one argument.