Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat:Code Organization #25

Merged
merged 14 commits into from
Nov 9, 2023
Merged
178 changes: 132 additions & 46 deletions docs/fsharp-cheatsheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Contents
- [Classes and Inheritance](#ClassesAndInheritance)
- [Interfaces and Object Expressions](#InterfacesAndObjectExpressions)
- [Active Patterns](#ActivePatterns)
- [Code Organization](#CodeOrganization)
- [Compiler Directives](#CompilerDirectives)

<a name="Comments"></a>Comments
Expand Down Expand Up @@ -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

Expand All @@ -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:

Expand All @@ -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 =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs to mvoe to L129

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm not understanding this comment. Here is lines 127 to 131:

127 Pipe operator `|>` is used to chain functions and arguments together. Double-backtick identifiers are handy to improve readability especially in unit testing:
128
129    let ``square, negate, then print`` x =
130       x |> square |> negate |> print
131

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was saying that there is/was print (square (negate x)), then print x |> square |> negate, then print >> square >> negate
And one of the fns has ' (prime) naming as it was the second.

if you remove the very first one, then the motivaiton for the second is lost
so prob best to move it down and work it into that section

(and consider the flow onward to showing it with >>)

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:

Expand All @@ -134,15 +132,15 @@ 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

Composition operator `>>` is used to compose functions:

let squareNegateThenPrint' =
SpiralOSS marked this conversation as resolved.
Show resolved Hide resolved
square >> negate >> print

### Recursive functions
The `rec` keyword is used together with the `let` keyword to define a recursive function:

Expand Down Expand Up @@ -172,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
Expand Down Expand Up @@ -206,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
Expand All @@ -218,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]
}
Expand All @@ -237,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
Expand All @@ -251,27 +249,27 @@ 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)

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
Expand All @@ -285,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
Expand All @@ -294,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
Expand Down Expand Up @@ -330,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
Expand All @@ -352,7 +349,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

<a name="#StaticallyResolvedTypeParameters"></a>Statically Resolved Type Parameters
<a name="StaticallyResolvedTypeParameters"></a>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.

Expand All @@ -365,13 +362,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.

Expand Down Expand Up @@ -459,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.
Expand All @@ -472,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) =
Expand All @@ -485,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
Expand Down Expand Up @@ -523,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
Expand All @@ -540,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.

<a name="CodeOrganization"></a>Code Organization
---------

### Modules
Modules are key building blocks for grouping related concepts; they can contain `types`, `let` bindings, and even other `modules`.
SpiralOSS marked this conversation as resolved.
Show resolved Hide resolved
Modules can be referenced using dot notation or exposed with the `open` keyword. Illustrative-only example:
SpiralOSS marked this conversation as resolved.
Show resolved Hide resolved

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.
SpiralOSS marked this conversation as resolved.
Show resolved Hide resolved

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.
SpiralOSS marked this conversation as resolved.
Show resolved Hide resolved

namespace MyNamespace

namespace MyNamespace.InnerSpace
SpiralOSS marked this conversation as resolved.
Show resolved Hide resolved
SpiralOSS marked this conversation as resolved.
Show resolved Hide resolved

They can also be part of a module name.
SpiralOSS marked this conversation as resolved.
Show resolved Hide resolved

module MyNamespace.InnerSpace.MyModule
SpiralOSS marked this conversation as resolved.
Show resolved Hide resolved

### 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
---
[<AutoOpen>]
SpiralOSS marked this conversation as resolved.
Show resolved Hide resolved
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`.
SpiralOSS marked this conversation as resolved.
Show resolved Hide resolved

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,
SpiralOSS marked this conversation as resolved.
Show resolved Hide resolved
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.
SpiralOSS marked this conversation as resolved.
Show resolved Hide resolved

module rec MyNamespace.MonkeyDomain

exception DoNotSqueezeBananaException of Banana // `Banana` has not been defined yet, and would fail without `rec`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a valid example, but very esoteric

I'd say most common usage of rec is people saying "well for this file, I just want to list everything alphabetically, regardless of the fact that it's normally good to put low level stuff first and then have the hihger level stuff at the bottom, with some exceptions where you use and to put some irrelevant dependencies underneath"

So while this does illustrate what it does and how to use it, I dont think it conveys what it's for really, so am pretty conflicted.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I stole this from MS Learn. I kind of hate it too. The long names get in the way of what's being demonstrated as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a walk and realized I'm conflicted because I hate exceptions. :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


type Banana = { Type: string; IsRipe: bool }
member self.Squeeze() = raise (DoNotSqueezeBananaException self)
SpiralOSS marked this conversation as resolved.
Show resolved Hide resolved
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.
SpiralOSS marked this conversation as resolved.
Show resolved Hide resolved

<a name="CompilerDirectives"></a>Compiler Directives
-------------------
Load another F# source file into FSI.
Expand All @@ -559,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
Expand Down