Bases
Comments
(* This is block
comment *)
// And this is line comment
/// XML doc summary
/// <summary>
/// Full XML doc
/// </summary>
Variables
Values for the purists
Keyword:
let
to declare/name a valueNo need for
;
at the end of the declarationCreates a Binding that is immutable by default
≃
const
in JS,readonly
members in C♯
Mutable binding with
let mutable
≃
let
en JS,var
en C♯⚠️ The assignment operator is
<-
, not=
used for equalityUse it sparingly, on a limited scope
let x = 1
x <- 2
// 💥 error FS0027: This value is not mutable. Consider using the mutable keyword...
let mutable x = 1
x <- 2 // ✅ OK
Traps ⚠️
Don't confuse the mutability of a variable (the part on the left-side of the binding) with the mutability of an object (the part on the right-side of the binding).
let
prevents the binding from being modified, i.e. assigning a new value to a variable.let
does not prevent an object from mutating......with one exception: mutable structures (e.g. ArrayCollector) working by value: when you call a method that modifies a structure (e.g.
Add
orClose
), you get a new copy. It'slet mutable
that allows you to keep a reference to this new value.
open FSharp.Core.CompilerServices
let collectorKo = ArrayCollector<int>()
collectorKo.Add(1)
collectorKo.Add(2)
let result = collectorKo.Close()
// val result: int array = [||]
let mutable collector = ArrayCollector<int>()
collector.Add(1)
collector.Add(2)
let result = collector.Close()
// result it: int array = [|1; 2|]
Dispose pattern
There are 2 ways to deal with an IDisposable
object in a logical block and automatically call its Dispose
method at the end of the block:
Replace
let
byuse
to bind theIDisposable
object to a variable nameEx #1:
use stream = new MemoryStream()
Ex #2:
use _ = logger.BeginScope(...)
Call the
using
function with theIDisposable
object and an action on itE.g.
using (new MemoryStream()) (fun stream -> ...)
Pros/Cons:
use
helps reduce the nesting/indentation level, but it's less explicit when the object is disposed.using
is more verbose, but also explicit.
Additional resources:
🔗 Resource Management: the use
keyword
🔗 using
function documentation
Names
Same constraints on variable naming as in C♯
... except the apostrophe
'
(tick)allowed in the middle or at the end of the name, but not at the beginning!
at the end of the name → indicates a variant (code convention)
Between double backticks
allow any character, in particular whitespaces, except line breaks
let x = 1
let x' = x + 1
// Works on keyword too! But avoid it because it's confusing!
let if' b t f = if b then t else f
let ``123 456`` = "123 456"
// 💡 Auto-completion : no need to enter the ``, just the 123
Shadowing
Use to redefine a value with a name already used above → The previous value is no longer accessible in the current scope
Not allowed at
module
level but allowed in a sub-scopeConvenient but can be misleading → Not recommended, except in special cases
let a = 2
let a = "ko" // 💥 Error FS0037: Duplicate definition of value 'a'
let b =
let a = "ok" // 👌 No compilation error
// `a` is bound to the "ok" string (not the previous value: 2)
// in all the rest of the b expression
let a = "ko" // 👌 Consecutive shadowings are possible!
...
Type Annotation
Optional thanks to inference
The type is declared after the name
name: type
(like in TypeScript)The value is mandatory, even with
mutable
which is a good constraint for the code 👍
let x = 1 // Type inferred (int)
let y: int = 2 // Type explicit
let z1: int
// 💥 Error FS0010: Incomplete structured construct at or before this point
// in binding. Expected '=' or other token.
let mutable z2: int
// 💥 Same error
Constant
What: Variable erased during compilation, every usage is replaced by the value
≃
const
C♯ - same idea thanconst enum
in TypeScript
How: Value decorated with the
Literal
attribute ⚠️ Attributes are between[< >]
→ It's a frequent beginner error to use[ ]
(like in C♯)Recommended naming convention : PascalCase
[<Literal>] // Line break required before the `let`
let AgeOfMajority = 18
let [<Literal>] Pi = 3.14 // Also possible but not recommended by MS/Fantomas formatter
Number
let pi = 3.14 // val pi : float = 3.14 • System.Double
let age = 18 // val age : int = 18 • System.Int32
let price = 5.95m // val price : decimal = 5.95M • System.Decimal
⚠️ No implicit conversion between number types
→ 💡 Use int
, float
, decimal
helper functions to do this conversion
let i = 1
i * 1.2;; // 💣 Error FS0001: The type 'float' does not match the type 'int'
float 3;; // val it : float = 3.0
decimal 3;; // val it : decimal = 3M
int 3.6;; // val it : int = 3
int "2";; // val it : int = 2
☝️ Note that this rule has been relaxed in some cases in F♯ 6.
String
let name = "Bob" // val name : string = "Bob"
// String formatting (available from the get go)
let name2 = sprintf "%s Marley" name // val name2 : string = "Bob Marley"
// String interpolation (F♯ 5)
let name3 = $"{name} Marley" // val name3 : string = "Bob Marley"
// Type safe string interpolation
let rank = 1
let name4 = $"%s{name} Marley, number %i{rank}"
// val name4: string = "Bob Marley, number 1"
// Access to a character by its index (>= 0) (F♯ 6)
let initial = name2[0] // val initial : char = 'B'
let initial = name2.[0] // Dot syntax, still supported
// String slicing (F♯ 6) (Alternative to x.Substring(index [, length]) method)
let firstName = name2[0..2] // val firstName : string = "Bob"
let lastName = name2[4..] // val lastName: string = "Marley"
Additional syntax:
// Verbatim string: idem C♯
let verbatimXml = @"<book title=""Paradise Lost"">"
// Triple-quoted string : no need to esapce the double-quotes `"`
let tripleXml = """<book title="Paradise Lost">"""
// Regular strings accept line breaks but do not trim whitespaces
let poemIndented = "
The lesser world was daubed
By a colorist of modest skill
A master limned you in the finest inks
And with a fresh-cut quill."
// Solution: backslash strings
// - Whitespaces (space and line break) are ignored between
// the \ terminating a line and the following non-whitespace character
// - hence the \n to add line breaks
let poem = "\
The lesser world was daubed\n\
By a colorist of modest skill\n\
A master limned you in the finest inks\n\
And with a fresh-cut quill."
// We can also combine line breaks and backslash strings:
let poemWithoutBackslashN = "\
The lesser world was daubed
\
By a colorist of modest skill
\
A master limned you in the finest inks
\
And with a fresh-cut quill."
String interpolation in F♯ 8
An interpolated string cannot contain braces ($"{xxx}"
) unless they are doubled ($"{{xxx}}"
). Since F♯ 8, the $ character is doubled ($$$
) to indicate the number of braces from which interpolation starts, respectively {{ }}
and {{{ }}}
.
let classAttr = "bold"
let cssNew = $$""".{{classAttr}}:hover {background-color: #eee;}"""
Conclusion: there are many many ways to write a string in F♯!
Encoding
String literals are encoded in Unicode:
let unicodeString1 = "abc" // val unicodeString1: string = "abc"
let unicodeString2 = "ab✅" // val unicodeString2: string = "ab✅"
We can work in ASCII using the B
suffix, but in this case we get a byte array
:
let asciiBytes = "abc"B
// val asciiBytes1: byte array = [|97uy; 98uy; 99uy|]
let asciiBytesKO = "ab🚫"B
// 💥 Error FS1140: This byte array literal contains characters
// that do not encode as a single byte
💡 Works also for character: 'a'B
Collections
Lists
Arrays
Sequences
Lists
A list is an immutable collection of elements of the same type. It's a different type than System.Collection.Generic.List<T>
BCL type. It's implemented internally as a linked list.
Creation with []
• Items separated by ;
or line breaks + indentation
let abc = [ 'a'; 'b'; 'c' ] // val abc : char list = ['a'; 'b'; 'c']
let a =
[ 2
3 ] // val a : int list = [2; 3]
⚠️ Trap: using ,
to separate items
E.g. [ 1, 2 ]
compiles but it is not a list of 2 items!
It's a list of 1 item, a tuple of 2 elements! 📍
☝ ML style type annotation: int list
= List<int>
→ Idiomatic only for some FSharp.Core
types: array
, list
, option
📍
List operators
::
Cons (for "construction"): add an item to the top of the list..
Range of numbers between min..max
(included) or min..step..max@
Append 2 lists
let ints = [2..5] // val ints : int list = [2; 3; 4; 5]
let ints' = 1 :: ints // val ints' : int list = [1; 2; 3; 4; 5]
let floats = [ 2. .. 5. ] // val floats: float list = [2.0; 3.0; 4.0; 5.0]
let chars = [ 'a' .. 'd' ] // val chars : char list = ['a'; 'b'; 'c'; 'd']
let chars' = chars @ [ 'e'; 'f' ] // val chars' : char list = ['a'; 'b'; 'c'; 'd'; 'e'; 'f']
let e = chars'[4] // val e: char = 'e'
⚠️ Space required before []
to create a list; otherwise: access by index
Arrays
Mutable fixed-size collections of elements of the same type.array 't
= 't[]
: BCL type
Creation with [||]
• Items separated by ;
or line breaks + indentation
let a1 = [| 'a'; 'b'; 'c' |]
// val a1: char array = [|'a'; 'b'; 'c'|]
let a2 =
[| 2
3 |]
// val a2: int array = [|2; 3|]
⚠️ Trap: []
used for list creation and array type!
Sequences
Series of elements of the same type't seq
= alias for System.Collections.Generic.IEnumerable<'t>
BCL type
Creation with seq { }
let seq1 = seq { 'a'; 'b'; 'c' }
// val seq1: char seq
let seq2 =
seq {
2
3
}
// val seq2: int seq
⚠️ Lazy: possible multiple enumeration • hence not evaluated in FSI console
Collections functions
Each type has its own module containing dedicated functions.
Common functions:
F♯ collections
C♯ LINQ (IEnumerable<_>
)
JS Array
map
, collect
Select()
, SelectMany()
map()
, flatMap()
exists
, forall
Any(predicate)
, All()
some()
, every()
filter
Where()
filter()
find
, tryFind
×
find()
fold
, reduce
Aggregate([seed]])
reduce()
average
, sum
Average()
, Sum()
×
🔗 Full documentation on fsharp.github.io: Array • List • Seq
Last updated
Was this helpful?