diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md
index bf4af2b..05c72c8 100644
--- a/docs/fsharp-cheatsheet.md
+++ b/docs/fsharp-cheatsheet.md
@@ -19,6 +19,7 @@ Contents
- [Classes and Inheritance](#ClassesAndInheritance)
- [Interfaces and Object Expressions](#InterfacesAndObjectExpressions)
- [Active Patterns](#ActivePatterns)
+- [Code Organization](#CodeOrganization)
- [Compiler Directives](#CompilerDirectives)
Comments
@@ -78,7 +79,7 @@ See [Strings (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/languag
------------------------
*Integer Prefixes* for hexadecimal, octal, or binary
- let numbers = (0x9F, 0o77, 0b1010) // (159, 63, 10)
+ let numbers = (0x9F, 0o77, 0b1010) // (159, 63, 10)
*Literal Type Suffixes* for integers, floats, decimals, and ascii arrays
@@ -93,18 +94,18 @@ See [Strings (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/languag
let bigInt = 9999999999999I // System.Numerics.BigInteger
- let float = 50.0f // signed 32-bit float
+ let float = 50.0f // signed 32-bit float
- let double = 50.0 // signed 64-bit float
+ let double = 50.0 // signed 64-bit float
- let scientific = 2.3E+32 // signed 64-bit float
+ let scientific = 2.3E+32 // signed 64-bit float
- let decimal = 50.0m // signed 128-bit decimal
+ let decimal = 50.0m // signed 128-bit decimal
- let byte = 'a'B // ascii character; 97uy
+ let byte = 'a'B // ascii character; 97uy
- let byteArray = "text"B // ascii string; [|116uy; 101uy; 120uy; 116uy|]
+ let byteArray = "text"B // ascii string; [|116uy; 101uy; 120uy; 116uy|]
*Primes* (or a tick `'` at the end of a label name) are idiomatic to functional languages and are included in F#. They are part of the identifier's name and simply indicate to the developer a variation of an existing value or function. For example:
@@ -122,9 +123,6 @@ The `let` keyword also defines named functions.
let square x = x * x
let print x = printfn "The number is: %d" 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:
@@ -134,7 +132,7 @@ Pipe operator `|>` is used to chain functions and arguments together. Double-bac
This operator can assist the F# type checker by providing type information before use:
let sumOfLengths (xs : string []) =
- xs
+ xs
|> Array.map (fun s -> s.Length)
|> Array.sum
@@ -142,8 +140,9 @@ Composition operator `>>` is used to compose functions:
let squareNegateThenPrint' =
square >> negate >> print
-
-### Recursive functions
+
+Functions
+### Recursive Functions
The `rec` keyword is used together with the `let` keyword to define a recursive function:
let rec fact x =
@@ -172,7 +171,7 @@ Pattern matching is often facilitated through `match` keyword.
In order to match sophisticated inputs, one can use `when` to create filters or guards on patterns:
- let sign x =
+ let sign x =
match x with
| 0 -> 0
| x when x < 0 -> -1
@@ -206,7 +205,7 @@ A *list* is an immutable collection of elements of the same type.
let list3 = list1 @ list2
// Recursion on list using (::) operator
- let rec sum list =
+ let rec sum list =
match list with
| [] -> 0
| x :: xs -> x + sum xs
@@ -218,17 +217,17 @@ A *list* is an immutable collection of elements of the same type.
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 =
+ let seq1 =
seq {
// "yield" adds one element
yield 1
yield 2
-
+
// "yield!" adds a whole subsequence
yield! [5..10]
}
@@ -237,11 +236,11 @@ A *sequence* is a logical series of elements of the same type. Individual sequen
The same list `[ 1; 3; 5; 7; 9 ]` or array `[| 1; 3; 5; 7; 9 |]` can be generated in various ways.
- Using range operator `..`
-
+
let xs = [ 1..2..9 ]
- Using list or array comprehensions
-
+
let ys = [| for i in 0..4 -> 2 * i + 1 |]
- Using `init` function
@@ -251,13 +250,13 @@ The same list `[ 1; 3; 5; 7; 9 ]` or array `[| 1; 3; 5; 7; 9 |]` can be generate
Lists and arrays have comprehensive sets of higher-order functions for manipulation.
- `fold` starts from the left of the list (or array) and `foldBack` goes in the opposite direction
-
+
let xs' =
- Array.fold (fun str n ->
+ Array.fold (fun str n ->
sprintf "%s,%i" str n) "" [| 0..9 |]
- `reduce` doesn't require an initial accumulator
-
+
let last xs = List.reduce (fun acc x -> x) xs
- `map` transforms every element of the list (or array)
@@ -265,13 +264,13 @@ Lists and arrays have comprehensive sets of higher-order functions for manipulat
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 {
+ seq {
for i in 0..9 do
printfn "Adding %d" i
yield i
@@ -285,7 +284,7 @@ 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
@@ -294,7 +293,7 @@ The first and second elements of a tuple can be obtained using `fst`, `snd`, or
let c' = fst (1, 2)
let d' = snd (1, 2)
-
+
let print' tuple =
match tuple with
| (a, b) -> printfn "Pair %A %A" a b
@@ -330,7 +329,6 @@ Records are essentially sealed classes with extra topping: default immutability,
| Node of Tree<'T> * 'T * Tree<'T>
| Leaf
-
let rec depth = function
| Node(l, _, r) -> 1 + max (depth l) (depth r)
| Leaf -> 0
@@ -352,7 +350,7 @@ Single-case discriminated unions are often used to create type-safe abstractions
// Use pattern matching to deconstruct single-case DU
let (Order id) = orderId
-Statically Resolved Type Parameters
+Statically Resolved Type Parameters
--------------------
A *statically resolved type parameter* is a type parameter that is replaced with an actual type at compile time instead of at run time. They are primarily useful in conjunction with member constraints.
@@ -365,13 +363,13 @@ A *statically resolved type parameter* is a type parameter that is replaced with
type RequestA = { Id: string; StringValue: string }
type RequestB = { Id: string; IntValue: int }
- let requestA : RequestA = { Id = "A"; StringValue = "Value" }
- let requestB : RequestB = { Id = "B"; IntValue = 42 }
+ let requestA: RequestA = { Id = "A"; StringValue = "Value" }
+ let requestB: RequestB = { Id = "B"; IntValue = 42 }
- let inline getIdOfRequest<'t when 't : (member Id: string)> (x: 't) = x.Id
+ let inline getId<'t when 't : (member Id: string)> (x: 't) = x.Id
- let idA = getIdOfRequest requestA // "A"
- let idB = getIdOfRequest requestB // "B"
+ let idA = getId requestA // "A"
+ let idB = getId requestB // "B"
See [Statically Resolved Type Parameters (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/generics/statically-resolved-type-parameters) and [Constraints (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/generics/constraints) for more examples.
@@ -459,7 +457,7 @@ Call a base class from a derived one.
*Upcasting* is denoted by `:>` operator.
- let dog = Dog()
+ let dog = Dog()
let animal = dog :> Animal
*Dynamic downcasting* (`:?>`) might throw an `InvalidCastException` if the cast doesn't succeed at runtime.
@@ -472,7 +470,7 @@ Declare `IVector` interface and implement it in `Vector'`.
type IVector =
abstract Scale : float -> IVector
-
+
type Vector'(x, y) =
interface IVector with
member __.Scale(s) =
@@ -485,7 +483,7 @@ 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
@@ -523,9 +521,9 @@ Another way of implementing interfaces is to use *object expressions*.
*Complete active patterns*:
- let (|Even|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
@@ -540,17 +538,156 @@ Another way of implementing interfaces is to use *object expressions*.
*Partial active patterns*:
- let (|DivisibleBy|_|) by n =
+ 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"
+
+ 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.
+Code Organization
+---------
+
+
+### Modules
+Modules are key building blocks for grouping related code; they can contain `types`, `let` bindings, or (nested) sub `module`s.
+Identifiers within modules can be referenced using dot notation, or you can bring them into scope via the `open` keyword. Illustrative-only example:
+
+ module Game =
+ let mutable basePoints = 1
+ type Player = { id: int; score: int }
+ let playerScored player = { player with score = player.score + basePoints }
+
+ let player: Game.Player = { id = 1; score = 0 }
+ let player' = Game.playerScored player // score = 1
+
+ open Game
+ basePoints <- 2
+ let player'' = playerScored player' // player''.score = 3
+
+If you have only one module in a file, the `module` name can be declared at the top of the file, with all further declarations
+being module elements (and non indentation required)
+
+ module Functions // notice there is no '=' when at the top of a file
+
+ let addToFive num = 5 + num
+ let subtractFive num = num - 5
+
+### Namespaces
+Namespaces are simply dotted names that prefix other program elements to allow for further hierarchical organization.
+All `type` and `module` elements that follow a `namespace` declaration will require an [`open`](#CodeOrganization_OpenAndAutoOpen) or to be dotted-into to access.
+If a new `namespace` is specified, all elements following will be part of the new namespace.
+
+ namespace MyNamespace
+
+ namespace MyNamespace.SubNamespace
+
+They can also be specified in a file-level [`module`](#CodeOrganization_Modules) definition, but no further `namespace` declarations may follow.
+
+ module MyNamespace.SubNamespace.Functions
+
+
+### Open and AutoOpen
+
+The `open` keyword can be used with `module`, `namespace`, and `type`.
+
+ open System.Diagnostics // open namespace
+ let stopwatch = Stopwatch.StartNew()
+ ---
+ module MyModule =
+ type DU1 = A | B | C
+ type DU2 = D | E | F
+
+ open type MyModule.DU1
+ let du1 = A
+ let duNotDefined = D // 'D' not defined
+ open MyModule
+ let du2 = D
+
+Available to `module` elements, is the `AutoOpen` attribute. This alleviates the need for an `open`; *however* this should be used cautiously,
+as all following declarations will be immediately brought into the global namespace and cause conflicts.
+
+ []
+ module MyModule =
+ type DU = A | B | C
+
+ let du = A
+
+### Accessibility Modifiers
+
+F# supports `public`, `private` (restraining the element to its containing `type` or `module`) and `internal` (restraining the element to its containing assembly).
+It can be applied to `module`, `let`, `member`, `type`, and `new`.
+
+With the exception of `let` bindings in a class `type`, everything defaults to `public`.
+
+| Element | Example with Modifier |
+|-------------------------------------------------------------------|--------------------------------------------|
+| Module | `module internal MyModule =` |
+| Module .. `let` | `let private value =` |
+| Record | `type internal MyRecord = { id: int }` |
+| Record [ctor](#CodeOrganization_PrivateConstructors) | `type MyRecord = private { id: int }` |
+| Discriminated Union | `type private MyDiscUni = A \| B` |
+| Discriminated Union [ctor](#CodeOrganization_PrivateConstructors) | `type MyDiscUni = internal A \| B ` |
+| Class | `type internal MyClass() =` |
+| Class [ctor](#CodeOrganization_PrivateConstructors) | `type MyClass private () =` |
+| Class Additional [ctor](#CodeOrganization_PrivateConstructors) | `internal new() = MyClass("defaultValue")` |
+| Class .. `let` | *Always private. Cannot be modified* |
+| `type` .. `member` | `member private _.classMember =` |
+
+
+##### Private Constructors
+
+Limiting `type` constructors (ctor) accessibility is a good way to enforce value integrity.
+
+Example of Single-case Discriminated Union with a `private` constructor:
+
+ type UnitQuantity =
+ private UnitQuantity of int
+ with
+ static member private MaxQty = 100
+ static member Create (qty:int) : Result =
+ if qty <= UnitQuantity.MaxQty
+ then Ok (UnitQuantity qty)
+ else Error $"UnitQuantity cannot be more than {UnitQuantity.MaxQty}"
+
+ let uQty = UnitQuantity.Create 50
+ match uQty with
+ | Ok (UnitQuantity qty) -> printfn $"Good: {qty}"
+ | Error errStr -> printfn $"Bad: {errStr}"
+
+Example of a class with a `private` constructor:
+
+ type MyClass private (count:int) =
+ member this.Count = count
+ static member CreateInstance (cnt: int) : MyClass option =
+ if cnt > 0
+ then Some(new MyClass(cnt))
+ else None
+
+ let myClass = MyClass.CreateInstance (5)
+
+### Recursive Reference
+
+F#'s type inference and name resolution runs in file and line order; by default, any forward references are considered as 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 you may need to loosen those rules.
+To do this we have `rec` for `module` and `namespace`s; and `and` for `type` and [`let`](#Functions_RecursiveFunctions) functions.
+
+ module rec MyNamespace.MonkeyDomain
+
+ exception DoNotSqueezeBananaException of Banana // `Banana` has not been defined yet, and would fail without `rec`
+
+ type Banana =
+ { Type: string; IsRipe: bool }
+ member self.Squeeze() = raise (DoNotSqueezeBananaException self)
+
+See [Namespaces (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/namespaces) and [Modules (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/modules) to learn more.
+
Compiler Directives
-------------------
Load another F# source file into FSI.
@@ -559,7 +696,7 @@ Load another F# source file into FSI.
Reference a .NET assembly (`/` symbol is recommended for Mono compatibility).
Reference a .NET assembly:
-
+
#r "../lib/FSharp.Markdown.dll"
Reference a nuget package