Skip to content

Commit da0586e

Browse files
authored
Update F# Tour (dotnet#1392)
* Update F# Tour * Feedback
1 parent ffc0530 commit da0586e

File tree

2 files changed

+768
-399
lines changed

2 files changed

+768
-399
lines changed

docs/fsharp/tour.md

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,47 +29,63 @@ The quickest way to run these code samples is to use [F# Interactive](tutorials/
2929

3030
The most fundamental pieces of any F# program are ***functions*** organized into ***modules***. [Functions](language-reference/functions/index.md) perform work on inputs to produce outputs, and they are organized under [Modules](language-reference/modules.md), which are the primary way you group things in F#. They are defined using the [`let` binding](language-reference/functions/let-bindings.md), which give the function a name and define its arguments.
3131

32-
[!code-fsharp[BasicFunctions](../../samples/snippets/fsharp/tour.fs#L25-L53)]
32+
[!code-fsharp[BasicFunctions](../../samples/snippets/fsharp/tour.fs#L101-L133)]
3333

3434
`let` bindings are also how you bind a value to a name, similar to a variable in other languages. `let` bindings are ***immutable*** by default, which means that once a value or function is bound to a name, it cannot be changed in-place. This is in contrast to variables in other languages, which are ***mutable***, meaning their values can be changed at any point in time. If you require a mutable binding, you can use `let mutable ...` syntax.
3535

36-
## Integers, Booleans, and Strings
36+
[!code-fsharp[Immutability](../../samples/snippets/fsharp/tour.fs#L75-L94)]
37+
38+
## Numbers, Booleans, and Strings
3739

3840
As a .NET language, F# supports the same underlying [primitive types](language-reference/primitive-types.md) that exist in .NET.
3941

40-
Here's some Integer values, generated in different ways:
42+
Here is how various numeric types are represented in F#:
4143

42-
[!code-fsharp[Integers](../../samples/snippets/fsharp/tour.fs#L8-L22)]
44+
[!code-fsharp[Numbers](../../samples/snippets/fsharp/tour.fs#L49-L68)]
4345

4446
Here's what Boolean values and performing basic conditional logic looks like:
4547

46-
[!code-fsharp[Bools](../../samples/snippets/fsharp/tour.fs#L61-L68)]
48+
[!code-fsharp[Bools](../../samples/snippets/fsharp/tour.fs#L142-L152)]
4749

4850
And here's what basic [string](language-reference/strings.md) manipulation looks like:
4951

50-
[!code-fsharp[Strings](../../samples/snippets/fsharp/tour.fs#L76-L93)]
52+
[!code-fsharp[Strings](../../samples/snippets/fsharp/tour.fs#L158-L180)]
5153

5254
## Tuples
5355

5456
[Tuples](language-reference/tuples.md) are a big deal in F#. They are a grouping of unnamed, but ordered values, that can be treated as values themselves. Think of them as values which are aggregated from other values. They have many uses, such as conveniently returning multiple values from a function, or grouping values for some ad-hoc convenience.
5557

56-
[!code-fsharp[Tuples](../../samples/snippets/fsharp/tour.fs#L101-L116)]
58+
[!code-fsharp[Tuples](../../samples/snippets/fsharp/tour.fs#L186-L203)]
59+
60+
As of F# 4.1, you can also create `struct` tuples. These also interoperate fully with C#7/Visual Basic 15 tuples, which are also `struct` tuples:
61+
62+
[!code-fsharp[Tuples](../../samples/snippets/fsharp/tour.fs#L205-L218)]
63+
64+
It's important to note that because `struct` tuples are value types, they are cannot be implicitly converted to reference tuples, or vice versa. You must explicitly convert between a reference and struct tuple.
65+
66+
## Pipelines and Composition
67+
68+
Pipe operators (`|>`, `<|`, `||>`, `<||`, `|||>`, `<|||`) and composition operators (`>>` and `<<`) are used extensively when processing data in F#. These operators are functions which allow you to establish "pipelines" of functions in a flexible manner. The following walks though how you could take advantage of these operators to build a simple functional pipeline.
69+
70+
[!code-fsharp[Pipelines](../../samples/snippets/fsharp/tour.fs#L227-L282)]
71+
72+
The above sample made use of many features of F#, including list processing functions, first-class functions, and [partial application](language-reference/functions/index.md#partial-application-of-arguments). Although a deep understanding of each of those concepts can become somewhat advanced, it should be clear how easily functions can be used to process data when building pipelines.
5773

5874
## Lists, Arrays, and Sequences
5975

6076
Lists, Arrays, and Sequences are three primary collection types in the F# core library.
6177

6278
[Lists](language-reference/lists.md) are ordered, immutable collections of elements of the same type. They are singly-linked lists, which means they are meant for enumeration, but a poor choice for random access and concatenation if they're large. This in contrast to Lists in other popular languages, which typically do not use a singly-linked list to represent Lists.
6379

64-
[!code-fsharp[Lists](../../samples/snippets/fsharp/tour.fs#L123-L162)]
80+
[!code-fsharp[Lists](../../samples/snippets/fsharp/tour.fs#L309-L359)]
6581

6682
[Arrays](language-reference/arrays.md) are fixed-size, *mutable* collections of elements of the same type. They support fast random access of elements, and are faster than F# lists because they are just contiguous blocks of memory.
6783

68-
[!code-fsharp[Arrays](../../samples/snippets/fsharp/tour.fs#L249-L283)]
84+
[!code-fsharp[Arrays](../../samples/snippets/fsharp/tour.fs#L368-L407)]
6985

7086
[Sequences](language-reference/sequences.md) are a logical series of elements, all of the same type. These are a more general type than Lists and Arrays, capable of being your "view" into any logical series of elements. They also stand out because can be ***lazy***, which means that elements can be computed only when they are needed.
7187

72-
[!code-fsharp[Sequences](../../samples/snippets/fsharp/tour.fs#L291-L328)]
88+
[!code-fsharp[Sequences](../../samples/snippets/fsharp/tour.fs#L418-L452)]
7389

7490
## Recursive Functions
7591

@@ -78,7 +94,7 @@ Processing collections or sequences of elements is typically done with [recursio
7894
>[!NOTE]
7995
The following example makes use of the pattern matching via the `match` expression. This fundamental construct is covered later in this article.
8096

81-
[!code-fsharp[RecursiveFunctions](../../samples/snippets/fsharp/tour.fs#L336-L364)]
97+
[!code-fsharp[RecursiveFunctions](../../samples/snippets/fsharp/tour.fs#L461-L500)]
8298

8399
F# also has full support for Tail Call Optimization, which is a way to optimize recursive calls so that they are just as fast as a loop construct.
84100

@@ -88,53 +104,70 @@ Record and Union types are two fundamental data types used in F# code, and are g
88104

89105
[Records](language-reference/records.md) are an aggregate of named values, with optional members (such as methods). If you're familiar with C# or Java, then these should feel similar to POCOs or POJOs - just with structural equality and less ceremony.
90106

91-
[!code-fsharp[Records](../../samples/snippets/fsharp/tour.fs#L372-L388)]
107+
[!code-fsharp[Records](../../samples/snippets/fsharp/tour.fs#L507-L559)]
108+
109+
As of F# 4.1, you can also represent Records as `struct`s. This is done with the `[<Struct>]` attribute:
110+
111+
[!code-fsharp[Records](../../samples/snippets/fsharp/tour.fs#L561-L568)]
112+
113+
[Discriminated Unions (DUs)](language-reference/discriminated-unions.md) are values which could be a number of named forms or cases. Data stored in the type can be one of several distinct values.
114+
115+
[!code-fsharp[Unions](../../samples/snippets/fsharp/tour.fs#L575-L631)]
116+
117+
You can also use DUs as *Single-Case Discriminated Unions*, to help with domain modeling over primitive types. Often times, strings and other primitive types are used to represent something, and are thus given a particular meaning. However, using only the primitive representation of the data can result in mistakenly assigning an incorrect value! Representing each type of information as a distinct single-case union can enforce correctness in this scenario.
118+
119+
[!code-fsharp[Unions](../../samples/snippets/fsharp/tour.fs#L633-L654)]
120+
121+
As the above sample demonstrates, to get the underlying value in a single-case Discriminated Union, you must explicitly unwrap it.
122+
123+
Additionally, DUs also support recursive definitions, allowing you to easily represent trees and inherently recursive data. For example, here's how you can represent a Binary Search Tree with `exists` and `insert` functions.
92124

93-
[Discriminated Unions](language-reference/discriminated-unions.md) are values which could be a number of named forms or cases. Data stored in the type can be one of several distinct values.
125+
[!code-fsharp[Unions](../../samples/snippets/fsharp/tour.fs#L656-L683)]
94126

95-
[!code-fsharp[Unions](../../samples/snippets/fsharp/tour.fs#L396-L447)]
127+
Because DUs allow you to represent the recursive structure of the tree in the data type, operating on this recursive structure is straightforward and guarantees correctness. It is also supported in pattern matching, as shown below.
96128

97-
You can also use Unions as *single-case unions*, to help with domain modeling over primitive types. Often times, strings and other primitive types are used to represent types of information that shouldn't be interchangeable. However, using only the string representation can lead to that mistake quite easily! Representing each type of information as a distinct single-case union can enforce correctness in this scenario.
129+
Additionally, you can represent DUs as `struct`s with the `[<Struct>]` attribute:
98130

99-
[!code-fsharp[Unions](../../samples/snippets/fsharp/tour.fs#L449-L452)]
131+
[!code-fsharp[Unions](../../samples/snippets/fsharp/tour.fs#L685-L696)]
100132

101-
Additionally, Discriminated Unions also support recursive definitions, allowing you to easily represent trees and inherently recursive data. For example, here's how you can represent a Binary Search Tree with `exists` and `insert` functions.
133+
However, there are two key things to keep in mind when doing so:
102134

103-
[!code-fsharp[Unions](../../samples/snippets/fsharp/tour.fs#L454-L483)]
135+
1. A struct DU cannot be recursively-defined.
136+
2. A struct DU must have unique names for each of its cases.
104137

105-
Because Discriminated Unions allow you to represent the recursive structure of the tree in the data type, operating on this recursive structure is straightforward and guarantees correctness. It is also supported in pattern matching, as shown below.
138+
Failure to follow the above will result in a compilation error.
106139

107140
## Pattern Matching
108141

109142
[Pattern Matching](language-reference/pattern-matching.md) is the F# language feature which enables correctness for operating on F# types. In the above samples, you probably noticed quite a bit of `match x with ...` syntax. This construct allows the compiler, which can understand the "shape" of data types, to force you to account for all possible cases when using a data type through what is known as Exhaustive Pattern Matching. This is incredibly powerful for correctness, and can be cleverly used to "lift" what would normally be a runtime concern into compile-time.
110143

111-
[!code-fsharp[PatternMatching](../../samples/snippets/fsharp/tour.fs#L515-L549)]
144+
[!code-fsharp[PatternMatching](../../samples/snippets/fsharp/tour.fs#L705-L739)]
112145

113146
You can also use the shorthand `function` construct for pattern matching, which is useful when you're writing functions which make use of [Partial Application](language-reference/functions/index.md#partial-application-of-arguments):
114147

115-
[!code-fsharp[PatternMatching](../../samples/snippets/fsharp/tour.fs#L551-L568)]
148+
[!code-fsharp[PatternMatching](../../samples/snippets/fsharp/tour.fs#L741-L759)]
116149

117-
Something you may have noticed is the use of the `_` pattern. This is known as the [Wildcard Pattern](language-reference/pattern-matching.md#wildcard-pattern), which is a way of saying "I don't care what something is". Although convenient, you can accidentally bypass Exhaustive Pattern Matching and no longer benefit from compile-time enforcements if you aren't careful in using `_`.
150+
Something you may have noticed is the use of the `_` pattern. This is known as the [Wildcard Pattern](language-reference/pattern-matching.md#wildcard-pattern), which is a way of saying "I don't care what something is". Although convenient, you can accidentally bypass Exhaustive Pattern Matching and no longer benefit from compile-time enforcements if you aren't careful in using `_`. It is best used when you don't care about certain pieces of a decomposed type when pattern matching, or the final clause when you have enumerated all meaningful cases in a pattern matching expression.
118151

119152
[Active Patterns](language-reference/active-patterns.md) are another powerful construct to use with pattern matching. They allow you to partition input data into custom forms, decomposing them at the pattern match call site. They can also be parameterized, thus allowing to define the partition as a function. Expanding the previous example to support Active Patterns looks something like this:
120153

121-
[!code-fsharp[ActivePatterns](../../samples/snippets/fsharp/tour.fs#L570-L582)]
154+
[!code-fsharp[ActivePatterns](../../samples/snippets/fsharp/tour.fs#L761-L783)]
122155

123156
## Optional Types
124157

125158
One special case of Discriminated Union types is the Option Type, which is so useful that it's a part of the F# core library.
126159

127-
[The Option Type](language-reference/options.md) is a type which represents one of two cases: a value, or nothing at all. It is used in any scenario where a value may or may not result from a particular operation. This then forces you to account for both cases, making it a compile-time concern rather than a runtime concern. These are often used in APIs where `null` is used to represent "nothing" instead, thus eliminating the need to worry about `NulReferenceException` in many circumstances.
160+
[The Option Type](language-reference/options.md) is a type which represents one of two cases: a value, or nothing at all. It is used in any scenario where a value may or may not result from a particular operation. This then forces you to account for both cases, making it a compile-time concern rather than a runtime concern. These are often used in APIs where `null` is used to represent "nothing" instead, thus eliminating the need to worry about `NullReferenceException` in many circumstances.
128161

129-
[!code-fsharp[Options](../../samples/snippets/fsharp/tour.fs#L489-L508)]
162+
[!code-fsharp[Options](../../samples/snippets/fsharp/tour.fs#L791-L811)]
130163

131164
## Units of Measure
132165

133166
One unique feature of F#'s type system is the ability to provide context for numeric literals through Units of Measure.
134167

135168
[Units of Measure](language-reference/units-of-measure.md) allow you to associate a numeric type to a unit, such as Meters, and have functions perform work on units rather than numeric literals. This enables the compiler to verify that the types of numeric literals passed in make sense under a certain context, and eliminate runtime errors associated with that kind of work.
136169

137-
[!code-fsharp[UnitsOfMeasure](../../samples/snippets/fsharp/tour.fs#L588-L604)]
170+
[!code-fsharp[UnitsOfMeasure](../../samples/snippets/fsharp/tour.fs#L818-L839)]
138171

139172
The F# Core library defines many SI unit types and unit conversions. To learn more, check out the [Microsoft.FSharp.Data.UnitSystems.SI Namespace](https://msdn.microsoft.com/visualfsharpdocs/conceptual/microsoft.fsharp.data.unitsystems.si-namespace-%5bfsharp%5d).
140173

@@ -144,15 +177,15 @@ F# also has full support for .NET classes, [Interfaces](language-reference/inter
144177

145178
[Classes](language-reference/classes.md) are types that represent .NET objects, which can have properties, methods, and events as its [Members](language-reference/members/index.md).
146179

147-
[!code-fsharp[Classes](../../samples/snippets/fsharp/tour.fs#L170-L194)]
180+
[!code-fsharp[Classes](../../samples/snippets/fsharp/tour.fs#L848-L877)]
148181

149182
Defining generic classes is also very straightforward.
150183

151-
[!code-fsharp[Classes](../../samples/snippets/fsharp/tour.fs#L202-L224)]
184+
[!code-fsharp[Classes](../../samples/snippets/fsharp/tour.fs#L884-L905)]
152185

153-
To implement an Interface, you can use `interface ... with` syntax.
186+
To implement an Interface, you can use either `interface ... with` syntax or an [Object Expression](language-reference/object-expressions.md).
154187

155-
[!code-fsharp[Classes](../../samples/snippets/fsharp/tour.fs#L232-L241)]
188+
[!code-fsharp[Classes](../../samples/snippets/fsharp/tour.fs#L912-L931)]
156189

157190
## Which Types to Use
158191

0 commit comments

Comments
 (0)