-
Notifications
You must be signed in to change notification settings - Fork 58
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
Changes from 5 commits
45827d1
e713c7d
69c2ee2
6833149
0f6645d
e5b5610
2f78852
7d334fa
b7c3ecd
1ff5c29
85ee3af
7c70dcc
0ae2eb1
3274abf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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,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: | ||
|
||
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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] | ||
} | ||
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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. | ||
|
||
|
@@ -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. | ||
|
||
|
@@ -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. | ||
|
@@ -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) = | ||
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. whereas I think it should have a link to https://eiriktsarpalis.wordpress.com/2017/02/19/youre-better-off-using-exceptions/ and https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/ :P |
||
|
||
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. | ||
|
@@ -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 | ||
|
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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:
There was a problem hiding this comment.
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))
, thenprint x |> square |> negate
, thenprint >> 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
>>
)