From 45827d19c66f7632ade0bd3dab6df5cc7db29e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9A=99=EF=B8=8E=20Greg?= Date: Tue, 24 Oct 2023 21:07:02 -0400 Subject: [PATCH 01/14] refactor:improved Statically Resolved Type Parameters example --- docs/fsharp-cheatsheet.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md index bf4af2b..1046e36 100644 --- a/docs/fsharp-cheatsheet.md +++ b/docs/fsharp-cheatsheet.md @@ -365,13 +365,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. From e713c7d196f1838c5db409190c0c8ba0b7aa1892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9A=99=EF=B8=8E=20Greg?= Date: Tue, 24 Oct 2023 23:09:17 -0400 Subject: [PATCH 02/14] refactor:removed useless example --- docs/fsharp-cheatsheet.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md index 1046e36..46f9c4c 100644 --- a/docs/fsharp-cheatsheet.md +++ b/docs/fsharp-cheatsheet.md @@ -122,9 +122,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: From 69c2ee2efe09836fbcbaea81a190ca10f349c5a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9A=99=EF=B8=8E=20Greg?= Date: Tue, 24 Oct 2023 23:09:44 -0400 Subject: [PATCH 03/14] feat:initial Code Organization --- docs/fsharp-cheatsheet.md | 49 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md index 46f9c4c..297f742 100644 --- a/docs/fsharp-cheatsheet.md +++ b/docs/fsharp-cheatsheet.md @@ -10,6 +10,7 @@ Contents - [Strings](#Strings) - [Basic Types and Literals](#BasicTypesAndLiterals) - [Functions](#Functions) +- [Code Organization](#CodeOrganization) - [Pattern Matching](#PatternMatching) - [Collections](#Collections) - [Tuples and Records](#TuplesAndRecords) @@ -157,6 +158,54 @@ The `rec` keyword is used together with the `let` keyword to define a recursive if x = 0 then false else even (x - 1) +Code Organization +--------- + +### Files + +The top of a .fs must start with `namespace`; `module`; or blank, but *only* blank if it is the final file of the project. Files are evaluated in the order they are listed in the project file. + +| Type | Can Follow With... | Example / Notes | +|-------------|--------------------------|------------------------------------------------------------------------------------------------------------| +| `namespace` | `type`,`exception` | `namespace MySpace.Domain` | +| | `module` | `module Types =`; fully-qualified: `MySpace.Domain.Types` | +| | `namespace` | Start a new namespace (`namespace MyNewSpace`) or nest (`namespace MySpace.Domain.Types`) | +| `module` | `type`,`exception`,`let` | `module MySpace.Domain.Types`;
`MySpace.Domain` is the namespace, and is optional | +| | `do` | Declarations that will be executed the first time only if and when something inside the module is accessed | +| | `module` | Nested Module. `module SubTypes =`; fully-qualified: `MySpace.Domain.Types.SubTypes` | +| empty | `type`,`exception`,`let` | Identical to a `module` with name set to basename of file | +| | `module` | `module Types =`; fully-qualified example for Program.fs: `Program.Types` | + +### Modules + +Modules can be nested and can contain types, values, and functions. Modules can be opened with `open` keyword. +When not at the top of a file, `module` is followed with an '=' and all declarations have to be indented. +(*For the OOP developer, `modules` are static classes*) + + module MyModule = + let x = 1 + let y = 2 + let z = 3 + + let sum = MyModule.x + MyModule.y + MyModule.z + + open MyModule + let sum' = x + y + z + +### Recursive Reference + +`namespace` and `module` (and `let`) support recursive references that can alleviate some frustration: + + namespace rec MySpace.Domain // could also be `module rec MySpace.Domain` + + 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. + Pattern Matching ---------------- Pattern matching is often facilitated through `match` keyword. From 68331494a43d1cdc0b28114e5cb783248a6b3de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9A=99=EF=B8=8E=20Greg?= Date: Tue, 24 Oct 2023 23:14:47 -0400 Subject: [PATCH 04/14] fix:anchor names for CodeOrganization and StaticallyResolvedTypeParameters --- docs/fsharp-cheatsheet.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md index 297f742..b2f8fbc 100644 --- a/docs/fsharp-cheatsheet.md +++ b/docs/fsharp-cheatsheet.md @@ -158,7 +158,7 @@ The `rec` keyword is used together with the `let` keyword to define a recursive if x = 0 then false else even (x - 1) -Code Organization +Code Organization --------- ### Files @@ -398,7 +398,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. From 0f6645d624cbfd86da375b1e692589af6888ab4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9A=99=EF=B8=8E=20Greg?= Date: Sun, 5 Nov 2023 16:06:04 -0500 Subject: [PATCH 05/14] refactor:reorganized Code Organization and moved to the bottom #23 --- docs/fsharp-cheatsheet.md | 212 ++++++++++++++++++++++---------------- 1 file changed, 126 insertions(+), 86 deletions(-) diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md index b2f8fbc..aae246a 100644 --- a/docs/fsharp-cheatsheet.md +++ b/docs/fsharp-cheatsheet.md @@ -10,7 +10,6 @@ Contents - [Strings](#Strings) - [Basic Types and Literals](#BasicTypesAndLiterals) - [Functions](#Functions) -- [Code Organization](#CodeOrganization) - [Pattern Matching](#PatternMatching) - [Collections](#Collections) - [Tuples and Records](#TuplesAndRecords) @@ -20,6 +19,7 @@ Contents - [Classes and Inheritance](#ClassesAndInheritance) - [Interfaces and Object Expressions](#InterfacesAndObjectExpressions) - [Active Patterns](#ActivePatterns) +- [Code Organization](#CodeOrganization) - [Compiler Directives](#CompilerDirectives) Comments @@ -79,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 @@ -94,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: @@ -132,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 @@ -140,7 +140,7 @@ 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: @@ -158,54 +158,6 @@ The `rec` keyword is used together with the `let` keyword to define a recursive if x = 0 then false else even (x - 1) -Code Organization ---------- - -### Files - -The top of a .fs must start with `namespace`; `module`; or blank, but *only* blank if it is the final file of the project. Files are evaluated in the order they are listed in the project file. - -| Type | Can Follow With... | Example / Notes | -|-------------|--------------------------|------------------------------------------------------------------------------------------------------------| -| `namespace` | `type`,`exception` | `namespace MySpace.Domain` | -| | `module` | `module Types =`; fully-qualified: `MySpace.Domain.Types` | -| | `namespace` | Start a new namespace (`namespace MyNewSpace`) or nest (`namespace MySpace.Domain.Types`) | -| `module` | `type`,`exception`,`let` | `module MySpace.Domain.Types`;
`MySpace.Domain` is the namespace, and is optional | -| | `do` | Declarations that will be executed the first time only if and when something inside the module is accessed | -| | `module` | Nested Module. `module SubTypes =`; fully-qualified: `MySpace.Domain.Types.SubTypes` | -| empty | `type`,`exception`,`let` | Identical to a `module` with name set to basename of file | -| | `module` | `module Types =`; fully-qualified example for Program.fs: `Program.Types` | - -### Modules - -Modules can be nested and can contain types, values, and functions. Modules can be opened with `open` keyword. -When not at the top of a file, `module` is followed with an '=' and all declarations have to be indented. -(*For the OOP developer, `modules` are static classes*) - - module MyModule = - let x = 1 - let y = 2 - let z = 3 - - let sum = MyModule.x + MyModule.y + MyModule.z - - open MyModule - let sum' = x + y + z - -### Recursive Reference - -`namespace` and `module` (and `let`) support recursive references that can alleviate some frustration: - - namespace rec MySpace.Domain // could also be `module rec MySpace.Domain` - - 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. - Pattern Matching ---------------- Pattern matching is often facilitated through `match` keyword. @@ -218,7 +170,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 @@ -252,7 +204,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 @@ -264,17 +216,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] } @@ -283,11 +235,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 @@ -297,13 +249,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) @@ -311,13 +263,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 @@ -331,7 +283,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 @@ -340,7 +292,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 @@ -376,7 +328,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 @@ -505,7 +456,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. @@ -518,7 +469,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) = @@ -531,7 +482,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 @@ -569,9 +520,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 @@ -586,17 +537,106 @@ 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 concepts; they can contain `types`, `let` bindings, and even other `modules`. +Modules can be referenced using dot notation or exposed with 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 your file, the `module` can be specified at the top of the file. + + 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 strings that prefix names. They are placed at the top of the file. +An inner namespace can be specified in the same file. + + namespace MyNamespace + + namespace MyNamespace.InnerSpace + +They can also be part of a module name. + + module MyNamespace.InnerSpace.MyModule + +### 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 + --- + [] + module MyModule = + type DU = A | B | C + + let du = A + +### Accessibility Modifiers + +F# supports `public`, `private` and `internal`. It can be applied to `module`, `let`, `member`, and `type`. + + module private MyModule = ... + --- + let private x = ... + --- + member private self.x = ... + --- + type private Person = ... + +### Recursive Reference + +F#'s dependency resolution is based on file and code order. This requirement encourages developers to think about the design of their programs and dependencies upfront, +which results in cleaner, maintainable code, but 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`s and `let` 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) + and + +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. @@ -605,7 +645,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 From e5b5610b7e8433e3537ca0ecc1c6aa35aeb11c73 Mon Sep 17 00:00:00 2001 From: Greg H <40124066+SpiralOSS@users.noreply.github.com> Date: Tue, 7 Nov 2023 07:19:45 -0500 Subject: [PATCH 06/14] Update docs/fsharp-cheatsheet.md Syntax/Grammar/Wording Co-authored-by: Ruben Bartelink --- docs/fsharp-cheatsheet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md index aae246a..5bf3c76 100644 --- a/docs/fsharp-cheatsheet.md +++ b/docs/fsharp-cheatsheet.md @@ -552,7 +552,7 @@ Another way of implementing interfaces is to use *object expressions*. --------- ### Modules -Modules are key building blocks for grouping related concepts; they can contain `types`, `let` bindings, and even other `modules`. +Modules are key building blocks for grouping related code; they can contain `types`, `let` bindings, or (nested) sub `module`s. Modules can be referenced using dot notation or exposed with the `open` keyword. Illustrative-only example: module Game = From 2f7885278c3e5f8798d91ba4aeb6eb1f3cdc90e8 Mon Sep 17 00:00:00 2001 From: Greg H <40124066+SpiralOSS@users.noreply.github.com> Date: Tue, 7 Nov 2023 07:20:07 -0500 Subject: [PATCH 07/14] Update docs/fsharp-cheatsheet.md Syntax/Grammar/Wording Co-authored-by: Ruben Bartelink --- docs/fsharp-cheatsheet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md index 5bf3c76..f8bf469 100644 --- a/docs/fsharp-cheatsheet.md +++ b/docs/fsharp-cheatsheet.md @@ -553,7 +553,7 @@ Another way of implementing interfaces is to use *object expressions*. ### Modules Modules are key building blocks for grouping related code; they can contain `types`, `let` bindings, or (nested) sub `module`s. -Modules can be referenced using dot notation or exposed with the `open` keyword. Illustrative-only example: +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 From 7d334faf94db246479acf2ceb3d2edcbfb64b46a Mon Sep 17 00:00:00 2001 From: Greg H <40124066+SpiralOSS@users.noreply.github.com> Date: Tue, 7 Nov 2023 07:20:45 -0500 Subject: [PATCH 08/14] Update docs/fsharp-cheatsheet.md Syntax/Grammar/Wording Co-authored-by: Ruben Bartelink --- docs/fsharp-cheatsheet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md index f8bf469..3f13b1d 100644 --- a/docs/fsharp-cheatsheet.md +++ b/docs/fsharp-cheatsheet.md @@ -567,7 +567,7 @@ Identifiers within modules can be referenced using dot notation, or you can brin basePoints <- 2 let player'' = playerScored player' // player''.score = 3 -If you have only one module in your file, the `module` can be specified at the top of the file. +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 From b7c3ecde1cdbbdefb8aee0492b152775277cab86 Mon Sep 17 00:00:00 2001 From: Greg H <40124066+SpiralOSS@users.noreply.github.com> Date: Tue, 7 Nov 2023 07:20:54 -0500 Subject: [PATCH 09/14] Update docs/fsharp-cheatsheet.md Syntax/Grammar/Wording Co-authored-by: Ruben Bartelink --- docs/fsharp-cheatsheet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md index 3f13b1d..5156994 100644 --- a/docs/fsharp-cheatsheet.md +++ b/docs/fsharp-cheatsheet.md @@ -580,7 +580,7 @@ An inner namespace can be specified in the same file. namespace MyNamespace - namespace MyNamespace.InnerSpace + namespace MyNamespace.SubNamespace They can also be part of a module name. From 1ff5c29318b609f3b1dd94e4a1d6b03b331ee06b Mon Sep 17 00:00:00 2001 From: Greg H <40124066+SpiralOSS@users.noreply.github.com> Date: Tue, 7 Nov 2023 07:24:59 -0500 Subject: [PATCH 10/14] Update docs/fsharp-cheatsheet.md Syntax/Grammar/Wording Co-authored-by: Ruben Bartelink --- docs/fsharp-cheatsheet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md index 5156994..7ad361d 100644 --- a/docs/fsharp-cheatsheet.md +++ b/docs/fsharp-cheatsheet.md @@ -584,7 +584,7 @@ An inner namespace can be specified in the same file. They can also be part of a module name. - module MyNamespace.InnerSpace.MyModule + module MyNamespace.SubNamespace.Functions ### Open and AutoOpen From 85ee3aff3ab2ec6cdf113a856ee5af6d8fd3182b Mon Sep 17 00:00:00 2001 From: Greg H <40124066+SpiralOSS@users.noreply.github.com> Date: Tue, 7 Nov 2023 07:31:47 -0500 Subject: [PATCH 11/14] Update docs/fsharp-cheatsheet.md Syntax/Grammar/Wording Co-authored-by: Ruben Bartelink --- docs/fsharp-cheatsheet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md index 7ad361d..109cc8b 100644 --- a/docs/fsharp-cheatsheet.md +++ b/docs/fsharp-cheatsheet.md @@ -623,7 +623,7 @@ F# supports `public`, `private` and `internal`. It can be applied to `module`, ### Recursive Reference -F#'s dependency resolution is based on file and code order. This requirement encourages developers to think about the design of their programs and dependencies upfront, +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, but 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`s and `let` functions. From 7c70dcce0ac02595424d7eed8280a491c3d90f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9A=99=EF=B8=8E=20Greg?= Date: Tue, 7 Nov 2023 11:32:12 -0500 Subject: [PATCH 12/14] feat:expanded Accessibility Modifiers significantly. Additional Code Organization wording tweaks --- docs/fsharp-cheatsheet.md | 86 +++++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 18 deletions(-) diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md index 109cc8b..d57cffb 100644 --- a/docs/fsharp-cheatsheet.md +++ b/docs/fsharp-cheatsheet.md @@ -141,7 +141,8 @@ 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 = @@ -551,6 +552,7 @@ Another way of implementing interfaces is to use *object expressions*. 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: @@ -567,7 +569,8 @@ Identifiers within modules can be referenced using dot notation, or you can brin 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) +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 @@ -575,17 +578,19 @@ If you have only one module in a file, the `module` name can be declared at the let subtractFive num = num - 5 ### Namespaces -Namespaces are simply dotted strings that prefix names. They are placed at the top of the file. -An inner namespace can be specified in the same file. +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 part of a module name. +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`. @@ -602,7 +607,10 @@ The `open` keyword can be used with `module`, `namespace`, and `type`. 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 @@ -611,29 +619,71 @@ The `open` keyword can be used with `module`, `namespace`, and `type`. ### Accessibility Modifiers -F# supports `public`, `private` and `internal`. It can be applied to `module`, `let`, `member`, and `type`. +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`. - module private MyModule = ... - --- - let private x = ... - --- - member private self.x = ... - --- - type private Person = ... +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, but 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`s and `let` functions. +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 } + with member self.Squeeze() = raise (DoNotSqueezeBananaException self) - and 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. From 0ae2eb1996967e5d7834d6fa114faf3c5d1ce45c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9A=99=EF=B8=8E=20Greg?= Date: Tue, 7 Nov 2023 11:35:27 -0500 Subject: [PATCH 13/14] refactor:changed Code Organization example changed to match F# styleguidelines --- docs/fsharp-cheatsheet.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md index d57cffb..4e679ce 100644 --- a/docs/fsharp-cheatsheet.md +++ b/docs/fsharp-cheatsheet.md @@ -681,8 +681,8 @@ To do this we have `rec` for `module` and `namespace`s; and `and` for `type` and exception DoNotSqueezeBananaException of Banana // `Banana` has not been defined yet, and would fail without `rec` - type Banana = { Type: string; IsRipe: bool } - with + 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. From 3274abff0e9aa318b8f6a124f6471eff5159d8e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9A=99=EF=B8=8E=20Greg?= Date: Wed, 8 Nov 2023 20:29:57 -0500 Subject: [PATCH 14/14] refactor:changed Code Organization Single case DU sample --- docs/fsharp-cheatsheet.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/fsharp-cheatsheet.md b/docs/fsharp-cheatsheet.md index 4e679ce..05c72c8 100644 --- a/docs/fsharp-cheatsheet.md +++ b/docs/fsharp-cheatsheet.md @@ -645,15 +645,16 @@ Limiting `type` constructors (ctor) accessibility is a good way to enforce value Example of Single-case Discriminated Union with a `private` constructor: - type UnitQuantity = private UnitQuantity of int + type UnitQuantity = + private UnitQuantity of int with - static member private maxQty = 100 - static member create (qty:int) : Result = - if qty <= UnitQuantity.maxQty + 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 + 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}"