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.