├── .gitattributes
├── .github
└── workflows
│ └── _build.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── docs
├── fsharp-cheatsheet.css
├── fsharp-cheatsheet.md
└── template.html
└── lib
└── StringParsing.fs
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.github/workflows/_build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on: [push]
3 | jobs:
4 |
5 | # BUILD
6 | build:
7 | name: Convert to HTML and PDF
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v4
11 |
12 | - uses: baileyjm02/markdown-to-pdf@v1
13 | with:
14 | input_dir: docs
15 | output_dir: build_outputs
16 | # images_dir: docs/images
17 | # for example
18 | # image_import: ./images
19 | # Default is true, can set to false to only get PDF files
20 | build_pdf: true
21 | build_html: true
22 | table_of_contents: true
23 | template: docs/template.html
24 |
25 | - name: Rename HTML file to index.html
26 | run: |
27 | sudo mv build_outputs/fsharp-cheatsheet.html build_outputs/index.html || echo "File not found, no rename needed"
28 |
29 | - uses: actions/upload-pages-artifact@v3
30 | with:
31 | path: build_outputs/
32 |
33 | # DEPLOY
34 | deploy:
35 | needs: build
36 |
37 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment
38 | permissions:
39 | pages: write # to deploy to Pages
40 | id-token: write # to verify the deployment originates from an appropriate source
41 |
42 | # Deploy to the github-pages environment
43 | environment:
44 | name: github-pages
45 | url: ${{ steps.deployment.outputs.page_url }}
46 |
47 | # Specify runner + deployment step
48 | runs-on: ubuntu-latest
49 | steps:
50 | - name: Deploy to GitHub Pages
51 | id: deployment
52 | uses: actions/deploy-pages@v4
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | fsharp-cheatsheet.tex
2 | index.html
3 | docs/output
4 | tools/temp
5 |
6 | *.aux
7 | *.synctex
8 | *.out
9 |
10 | .temp/
11 |
12 | #################
13 | ## IntelliJ
14 | #################
15 | .idea/
16 |
17 | #################
18 | ## Eclipse
19 | #################
20 |
21 | *.pydevproject
22 | .project
23 | .metadata
24 | bin/
25 | tmp/
26 | *.tmp
27 | *.bak
28 | *.swp
29 | *~.nib
30 | local.properties
31 | .classpath
32 | .settings/
33 | .loadpath
34 |
35 | # External tool builders
36 | .externalToolBuilders/
37 |
38 | # Locally stored "Eclipse launch configurations"
39 | *.launch
40 |
41 | # CDT-specific
42 | .cproject
43 |
44 | # PDT-specific
45 | .buildpath
46 |
47 |
48 | #################
49 | ## Visual Studio
50 | #################
51 |
52 | # Visual Studio cache/options directory
53 | .vs/
54 |
55 | ## Ignore Visual Studio temporary files, build results, and
56 | ## files generated by popular Visual Studio add-ons.
57 |
58 | # User-specific files
59 | *.suo
60 | *.user
61 | *.sln.docstates
62 |
63 | # Build results
64 |
65 | [Dd]ebug/
66 | [Rr]elease/
67 | x64/
68 | build/
69 | [Bb]in/
70 | [Oo]bj/
71 | packages/
72 |
73 | # MSTest test Results
74 | [Tt]est[Rr]esult*/
75 | [Bb]uild[Ll]og.*
76 |
77 | *_i.c
78 | *_p.c
79 | *.ilk
80 | *.meta
81 | *.obj
82 | *.pch
83 | *.pdb
84 | *.pgc
85 | *.pgd
86 | *.rsp
87 | *.sbr
88 | *.tlb
89 | *.tli
90 | *.tlh
91 | *.tmp
92 | *.tmp_proj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.log
99 | *.scc
100 |
101 | # Visual C++ cache files
102 | ipch/
103 | *.aps
104 | *.ncb
105 | *.opensdf
106 | *.sdf
107 | *.cachefile
108 |
109 | # Visual Studio profiler
110 | *.psess
111 | *.vsp
112 | *.vspx
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 |
121 | # TeamCity is a build add-in
122 | _TeamCity*
123 |
124 | # DotCover is a Code Coverage Tool
125 | *.dotCover
126 |
127 | # NCrunch
128 | *.ncrunch*
129 | .*crunch*.local.xml
130 |
131 | # Installshield output folder
132 | [Ee]xpress/
133 |
134 | # DocProject is a documentation generator add-in
135 | DocProject/buildhelp/
136 | DocProject/Help/*.HxT
137 | DocProject/Help/*.HxC
138 | DocProject/Help/*.hhc
139 | DocProject/Help/*.hhk
140 | DocProject/Help/*.hhp
141 | DocProject/Help/Html2
142 | DocProject/Help/html
143 |
144 | # Click-Once directory
145 | publish/
146 |
147 | # Publish Web Output
148 | *.Publish.xml
149 | *.pubxml
150 |
151 | # NuGet Packages Directory
152 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
153 | #packages/
154 |
155 | # Windows Azure Build Output
156 | csx
157 | *.build.csdef
158 |
159 | # Windows Store app package directory
160 | AppPackages/
161 |
162 | # Others
163 | sql/
164 | *.Cache
165 | ClientBin/
166 | [Ss]tyle[Cc]op.*
167 | ~$*
168 | *~
169 | *.dbmdl
170 | *.[Pp]ublish.xml
171 | *.pfx
172 | *.publishsettings
173 |
174 | # RIA/Silverlight projects
175 | Generated_Code/
176 |
177 | # Backup & report files from converting an old project file to a newer
178 | # Visual Studio version. Backup files are not needed, because we have git ;-)
179 | _UpgradeReport_Files/
180 | Backup*/
181 | UpgradeLog*.XML
182 | UpgradeLog*.htm
183 |
184 | # SQL Server files
185 | App_Data/*.mdf
186 | App_Data/*.ldf
187 |
188 | #############
189 | ## Windows detritus
190 | #############
191 |
192 | # Windows image file caches
193 | Thumbs.db
194 | ehthumbs.db
195 |
196 | # Folder config file
197 | Desktop.ini
198 |
199 | # Recycle Bin used on file shares
200 | $RECYCLE.BIN/
201 |
202 | # Mac crap
203 | .DS_Store
204 |
205 |
206 | #############
207 | ## Python
208 | #############
209 |
210 | *.py[co]
211 |
212 | # Packages
213 | *.egg
214 | *.egg-info
215 | dist/
216 | build/
217 | eggs/
218 | parts/
219 | var/
220 | sdist/
221 | develop-eggs/
222 | .installed.cfg
223 |
224 | # Installer logs
225 | pip-log.txt
226 |
227 | # Unit test / coverage reports
228 | .coverage
229 | .tox
230 |
231 | #Translations
232 | *.mo
233 |
234 | #Mr Developer
235 | .mr.developer.cfg
236 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013, [Anh-Dung Phan](http://lonelypad.blogspot.dk/)
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
15 | ------------------------------------------------------------
16 |
17 | Apache License, Version 2.0
18 | ===========================
19 |
20 | Apache License
21 | Version 2.0, January 2004
22 | http://www.apache.org/licenses/
23 |
24 | ### TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
25 |
26 |
27 | **1. Definitions.**
28 |
29 | - "License" shall mean the terms and conditions for use, reproduction,
30 | and distribution as defined by Sections 1 through 9 of this document.
31 |
32 | - "Licensor" shall mean the copyright owner or entity authorized by
33 | the copyright owner that is granting the License.
34 |
35 | - "Legal Entity" shall mean the union of the acting entity and all
36 | other entities that control, are controlled by, or are under common
37 | control with that entity. For the purposes of this definition,
38 | "control" means (i) the power, direct or indirect, to cause the
39 | direction or management of such entity, whether by contract or
40 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
41 | outstanding shares, or (iii) beneficial ownership of such entity.
42 |
43 | - "You" (or "Your") shall mean an individual or Legal Entity
44 | exercising permissions granted by this License.
45 |
46 | - "Source" form shall mean the preferred form for making modifications,
47 | including but not limited to software source code, documentation
48 | source, and configuration files.
49 |
50 | - "Object" form shall mean any form resulting from mechanical
51 | transformation or translation of a Source form, including but
52 | not limited to compiled object code, generated documentation,
53 | and conversions to other media types.
54 |
55 | - "Work" shall mean the work of authorship, whether in Source or
56 | Object form, made available under the License, as indicated by a
57 | copyright notice that is included in or attached to the work
58 | (an example is provided in the Appendix below).
59 |
60 | - "Derivative Works" shall mean any work, whether in Source or Object
61 | form, that is based on (or derived from) the Work and for which the
62 | editorial revisions, annotations, elaborations, or other modifications
63 | represent, as a whole, an original work of authorship. For the purposes
64 | of this License, Derivative Works shall not include works that remain
65 | separable from, or merely link (or bind by name) to the interfaces of,
66 | the Work and Derivative Works thereof.
67 |
68 | - "Contribution" shall mean any work of authorship, including
69 | the original version of the Work and any modifications or additions
70 | to that Work or Derivative Works thereof, that is intentionally
71 | submitted to Licensor for inclusion in the Work by the copyright owner
72 | or by an individual or Legal Entity authorized to submit on behalf of
73 | the copyright owner. For the purposes of this definition, "submitted"
74 | means any form of electronic, verbal, or written communication sent
75 | to the Licensor or its representatives, including but not limited to
76 | communication on electronic mailing lists, source code control systems,
77 | and issue tracking systems that are managed by, or on behalf of, the
78 | Licensor for the purpose of discussing and improving the Work, but
79 | excluding communication that is conspicuously marked or otherwise
80 | designated in writing by the copyright owner as "Not a Contribution."
81 |
82 | - "Contributor" shall mean Licensor and any individual or Legal Entity
83 | on behalf of whom a Contribution has been received by Licensor and
84 | subsequently incorporated within the Work.
85 |
86 | **2. Grant of Copyright License.**
87 | Subject to the terms and conditions of
88 | this License, each Contributor hereby grants to You a perpetual,
89 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
90 | copyright license to reproduce, prepare Derivative Works of,
91 | publicly display, publicly perform, sublicense, and distribute the
92 | Work and such Derivative Works in Source or Object form.
93 |
94 | **3. Grant of Patent License.**
95 | Subject to the terms and conditions of
96 | this License, each Contributor hereby grants to You a perpetual,
97 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
98 | (except as stated in this section) patent license to make, have made,
99 | use, offer to sell, sell, import, and otherwise transfer the Work,
100 | where such license applies only to those patent claims licensable
101 | by such Contributor that are necessarily infringed by their
102 | Contribution(s) alone or by combination of their Contribution(s)
103 | with the Work to which such Contribution(s) was submitted. If You
104 | institute patent litigation against any entity (including a
105 | cross-claim or counterclaim in a lawsuit) alleging that the Work
106 | or a Contribution incorporated within the Work constitutes direct
107 | or contributory patent infringement, then any patent licenses
108 | granted to You under this License for that Work shall terminate
109 | as of the date such litigation is filed.
110 |
111 | **4. Redistribution.**
112 | You may reproduce and distribute copies of the
113 | Work or Derivative Works thereof in any medium, with or without
114 | modifications, and in Source or Object form, provided that You
115 | meet the following conditions:
116 |
117 | - You must give any other recipients of the Work or
118 | Derivative Works a copy of this License; and
119 |
120 | - You must cause any modified files to carry prominent notices
121 | stating that You changed the files; and
122 |
123 | - You must retain, in the Source form of any Derivative Works
124 | that You distribute, all copyright, patent, trademark, and
125 | attribution notices from the Source form of the Work,
126 | excluding those notices that do not pertain to any part of
127 | the Derivative Works; and
128 |
129 | - If the Work includes a "NOTICE" text file as part of its
130 | distribution, then any Derivative Works that You distribute must
131 | include a readable copy of the attribution notices contained
132 | within such NOTICE file, excluding those notices that do not
133 | pertain to any part of the Derivative Works, in at least one
134 | of the following places: within a NOTICE text file distributed
135 | as part of the Derivative Works; within the Source form or
136 | documentation, if provided along with the Derivative Works; or,
137 | within a display generated by the Derivative Works, if and
138 | wherever such third-party notices normally appear. The contents
139 | of the NOTICE file are for informational purposes only and
140 | do not modify the License. You may add Your own attribution
141 | notices within Derivative Works that You distribute, alongside
142 | or as an addendum to the NOTICE text from the Work, provided
143 | that such additional attribution notices cannot be construed
144 | as modifying the License.
145 |
146 | You may add Your own copyright statement to Your modifications and
147 | may provide additional or different license terms and conditions
148 | for use, reproduction, or distribution of Your modifications, or
149 | for any such Derivative Works as a whole, provided Your use,
150 | reproduction, and distribution of the Work otherwise complies with
151 | the conditions stated in this License.
152 |
153 | **5. Submission of Contributions.**
154 | Unless You explicitly state otherwise,
155 | any Contribution intentionally submitted for inclusion in the Work
156 | by You to the Licensor shall be under the terms and conditions of
157 | this License, without any additional terms or conditions.
158 | Notwithstanding the above, nothing herein shall supersede or modify
159 | the terms of any separate license agreement you may have executed
160 | with Licensor regarding such Contributions.
161 |
162 | **6. Trademarks.**
163 | This License does not grant permission to use the trade
164 | names, trademarks, service marks, or product names of the Licensor,
165 | except as required for reasonable and customary use in describing the
166 | origin of the Work and reproducing the content of the NOTICE file.
167 |
168 | **7. Disclaimer of Warranty.**
169 | Unless required by applicable law or
170 | agreed to in writing, Licensor provides the Work (and each
171 | Contributor provides its Contributions) on an "AS IS" BASIS,
172 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
173 | implied, including, without limitation, any warranties or conditions
174 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
175 | PARTICULAR PURPOSE. You are solely responsible for determining the
176 | appropriateness of using or redistributing the Work and assume any
177 | risks associated with Your exercise of permissions under this License.
178 |
179 | **8. Limitation of Liability.**
180 | In no event and under no legal theory,
181 | whether in tort (including negligence), contract, or otherwise,
182 | unless required by applicable law (such as deliberate and grossly
183 | negligent acts) or agreed to in writing, shall any Contributor be
184 | liable to You for damages, including any direct, indirect, special,
185 | incidental, or consequential damages of any character arising as a
186 | result of this License or out of the use or inability to use the
187 | Work (including but not limited to damages for loss of goodwill,
188 | work stoppage, computer failure or malfunction, or any and all
189 | other commercial damages or losses), even if such Contributor
190 | has been advised of the possibility of such damages.
191 |
192 | **9. Accepting Warranty or Additional Liability.**
193 | While redistributing
194 | the Work or Derivative Works thereof, You may choose to offer,
195 | and charge a fee for, acceptance of support, warranty, indemnity,
196 | or other liability obligations and/or rights consistent with this
197 | License. However, in accepting such obligations, You may act only
198 | on Your own behalf and on Your sole responsibility, not on behalf
199 | of any other Contributor, and only if You agree to indemnify,
200 | defend, and hold each Contributor harmless for any liability
201 | incurred by, or claims asserted against, such Contributor by reason
202 | of your accepting any such warranty or additional liability.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | fsharp-cheatsheet
2 | =================
3 |
4 | An F# Cheatsheet in Markup and HTML formats
5 |
6 |
7 | - [View the cheatsheet online](https://fsprojects.github.io/fsharp-cheatsheet)
8 | - [Download the PDF](https://fsprojects.github.io/fsharp-cheatsheet/fsharp-cheatsheet.pdf)
9 |
10 | ## Contributing
11 |
12 | The raw content of this cheatsheet can be edited at [fsharp-cheatsheet.md](docs/fsharp-cheatsheet.md).
13 |
14 | This resource was, is, and should remain a community effort. If you're learning F# and something you discovered was important wasn't covered, you're the very best person to point that out and phrase it in a way that someone new can get running with.
15 |
16 | The more diversity of reviewers we get, the better. That said, the best PRs are small ones that touch one piece at a time and can hence be reviewed and merged rapidly.
17 |
18 | Primary maintainer: @SpiralOSS (backup: @bartelink)
19 |
20 | ## Acknowledgements
21 |
22 | Anh-Dung Phan (@dungpa) created and maintained the repo for many years before donating it to community ownership under fsprojects
23 |
24 | The original inspiration was the (now obsolete) http://www.samskivert.com/code/fsharp/fsharp-cheat-sheet.pdf
25 |
26 | ## License
27 |
28 | The documents are available under Apache 2.0 license.
29 | For more information see the [License file](LICENSE.md).
30 |
--------------------------------------------------------------------------------
/docs/fsharp-cheatsheet.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Arial', sans-serif;
3 | margin: 40px auto;
4 | max-width: 800px;
5 | line-height: 1.6;
6 | color: #333;
7 | background-color: #f7f7f7;
8 | }
9 |
10 | h1, h2, h3, h4, h5, h6 {
11 | margin-top: 1.2em;
12 | }
13 |
14 | h1 {
15 | font-size: 2em;
16 | border-bottom: 2px solid #ddd;
17 | }
18 |
19 | h2 {
20 | font-size: 1.5em;
21 | }
22 |
23 | h3 {
24 | font-size: 1.3em;
25 | }
26 |
27 | p {
28 | margin-bottom: 1.2em;
29 | }
30 |
31 | a {
32 | color: #0077cc;
33 | text-decoration: none;
34 | }
35 |
36 | a:hover {
37 | text-decoration: underline;
38 | }
39 |
40 | strong {
41 | font-weight: bold;
42 | }
43 |
44 | em {
45 | font-style: italic;
46 | }
47 |
48 | ul, ol {
49 | margin: 20px 0;
50 | padding-left: 40px;
51 | }
52 |
53 | li {
54 | margin-bottom: 10px;
55 | }
56 |
57 | blockquote {
58 | border-left: 4px solid #ddd;
59 | padding-left: 15px;
60 | margin: 20px 0;
61 | font-style: italic;
62 | }
63 |
64 | code {
65 | font-family: 'Courier New', monospace;
66 | background-color: #eee;
67 | padding: 2px 5px;
68 | border-radius: 3px;
69 | }
70 |
71 | pre {
72 | font-family: 'Courier New', monospace;
73 | background-color: #eee;
74 | padding: 10px;
75 | border-radius: 3px;
76 | overflow-x: auto;
77 | }
78 |
--------------------------------------------------------------------------------
/docs/fsharp-cheatsheet.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Comments
4 |
5 | Block comments are placed between `(*` and `*)`. Line comments start from `//` and continue until the end of the line.
6 |
7 | ```fsharp
8 | (* This is block comment *)
9 |
10 | // And this is a line comment
11 | ```
12 |
13 | XML doc comments come after `///` allowing us to use XML tags to generate documentation.
14 |
15 | ```fsharp
16 | /// Double a number and add 1
17 | let myFunction n = n * 2 + 1
18 | ```
19 |
20 |
21 |
22 | # Strings
23 |
24 | F# `string` type is an alias for `System.String` type.
25 |
26 | ```fsharp
27 | // Create a string using string concatenation
28 | let hello = "Hello" + " World"
29 | ```
30 |
31 | Use *verbatim strings* preceded by `@` symbol to avoid escaping control characters (except escaping `"` by `""`).
32 |
33 | ```fsharp
34 | let verbatimXml = @""
35 | ```
36 |
37 | We don't even have to escape `"` with *triple-quoted strings*.
38 |
39 | ```fsharp
40 | let tripleXml = """"""
41 | ```
42 |
43 | *Backslash strings* indent string contents by stripping leading spaces.
44 |
45 | ```fsharp
46 | let poem =
47 | "The lesser world was daubed\n\
48 | By a colorist of modest skill\n\
49 | A master limned you in the finest inks\n\
50 | And with a fresh-cut quill."
51 | ```
52 |
53 | *String Slicing* is supported by using `[start..end]` syntax.
54 |
55 | ```fsharp
56 | let str = "Hello World"
57 | let firstWord = str[0..4] // "Hello"
58 | let lastWord = str[6..] // "World"
59 | ```
60 |
61 | *String Interpolation* is supported by prefixing the string with `$` symbol. All of these will output `"Hello" \ World!`:
62 |
63 | ```fsharp
64 | let expr = "Hello"
65 | printfn " \"%s\" \\ World!" expr
66 | printfn $" \"{expr}\" \\ World!"
67 | printfn $" \"%s{expr}\" \\ World!" // using a format specifier
68 | printfn $@" ""{expr}"" \ World!"
69 | printfn $@" ""%s{expr}"" \ World!"
70 | printf $@" ""%s{expr}"" \ World!" // no newline
71 | ```
72 |
73 | See [Strings (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/strings) for more on escape characters, byte arrays, and format specifiers.
74 |
75 |
76 |
77 | # Basic Types and Literals
78 |
79 | Use the `let` keyword to define values. Values are immutable by default, but can be modified if specified with the `mutable` keyword.
80 |
81 | ```fsharp
82 | let myStringValue = "my string"
83 | let myIntValue = 10
84 | let myExplicitlyTypedIntValue: int = 10
85 | let mutable myMutableInt = 10
86 | myMutableInt <- 11 // use <- arrow to assign a new value
87 | ```
88 |
89 | *Integer Prefixes* for hexadecimal, octal, or binary
90 |
91 | ```fsharp
92 | let numbers = (0x9F, 0o77, 0b1010) // (159, 63, 10)
93 | ```
94 |
95 | *Literal Type Suffixes* for integers, floats, decimals, and ascii arrays
96 |
97 | ```fsharp
98 | let ( sbyte, byte ) = ( 55y, 55uy ) // 8-bit integer
99 |
100 | let ( short, ushort ) = ( 50s, 50us ) // 16-bit integer
101 |
102 | let ( int, uint ) = ( 50, 50u ) // 32-bit integer
103 |
104 | let ( long, ulong ) = ( 50L, 50uL ) // 64-bit integer
105 |
106 | let bigInt = 9999999999999I // System.Numerics.BigInteger
107 |
108 | let float = 50.0f // signed 32-bit float
109 |
110 | let double = 50.0 // signed 64-bit float
111 |
112 | let scientific = 2.3E+32 // signed 64-bit float
113 |
114 | let decimal = 50.0m // signed 128-bit decimal
115 |
116 | let byte = 'a'B // ascii character; 97uy
117 |
118 | let byteArray = "text"B // ascii string; [|116uy; 101uy; 120uy; 116uy|]
119 | ```
120 |
121 | *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 |
123 | ```fsharp
124 | let x = 5
125 | let x' = x + 1
126 | let x'' = x' + 1
127 | ```
128 |
129 | See [Literals (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/literals) for complete reference.
130 |
131 |
132 |
133 | # Functions
134 |
135 | ## `let` bindings
136 |
137 | Use the `let` keyword to define named functions.
138 |
139 | ```fsharp
140 | let add n1 n2 = n1 + n2
141 | let subtract n1 n2 = n1 - n2
142 | let negate num = -1 * num
143 | let print num = printfn $"The number is: {num}"
144 | ```
145 | ## Pipe and Composition Operators
146 |
147 | Pipe operator `|>` is used to chain functions and arguments together.
148 |
149 | ```fsharp
150 | let addTwoSubtractTwoNegateAndPrint num =
151 | num |> add 2 |> subtract 2 |> negate |> print
152 | ```
153 |
154 | Composition operator `>>` is used to compose functions:
155 |
156 | ```fsharp
157 | let addTwoSubtractTwoNegateAndPrint' =
158 | add 2 >> subtract 2 >> negate >> print
159 | ```
160 |
161 | Caution: The output is the _last_ argument to the next function.
162 |
163 | ```fsharp
164 | // `addTwoSubtractTwoNegateAndPrint 10` becomes:
165 | 10
166 | |> add 2 // 2 + 10 = 12
167 | |> subtract 2 // 2 - 12 = -10
168 | |> negate // -1 * -10 = 10
169 | |> print // "The number is 10"
170 | ```
171 |
172 | ## Anonymous functions
173 |
174 | Anonymous, or "lambda" functions, are denoted by the `fun` keyword and the arrow operator `->`.
175 |
176 | ```fsharp
177 | let isDescending xs =
178 | xs
179 | |> List.pairwise
180 | |> List.forAll (fun (x, y) -> x > y)
181 |
182 | let suspiciousRecords =
183 | records
184 | |> Seq.filter (fun x -> x.Age >= 150)
185 | ```
186 | ### _.Property shorthand
187 |
188 | If the lambda function has a single argument that is used in an atomic expression, the following shorthand has been available since F# 8:
189 |
190 | ```fsharp
191 | let names =
192 | people
193 | |> List.map (fun person -> person.Name) // regular lambda expression
194 |
195 | let names' =
196 | people
197 | |> List.map _.Name // _.Property shorthand
198 | ```
199 |
200 | You may chain properties and methods together, so long as there is no "space" in the expression. E.g.:
201 |
202 | ```fsharp
203 | let uppercaseNames =
204 | people |> List.map _.Name.ToUpperInvariant()
205 | ```
206 |
207 |
208 |
209 | ## `unit` Type
210 |
211 | The `unit` type is a type that indicates the absence of a specific value. It is represented by `()`.
212 | The most common use is when you have a function that receives no parameters, but you need it to evaluate on every call:
213 |
214 | ```fsharp
215 | // Without unit, DateTime.Now is only evaluated once. The return value will never change.
216 | let getCurrentDateTime = DateTime.Now
217 |
218 | // This version evalautes DateTime.Now every time you call it with a `unit` argument.
219 | let getCurrentDateTime2 () = DateTime.Now
220 |
221 | // How to call the function:
222 | let startTime = getCurrentDateTime2()
223 | ```
224 |
225 |
226 |
227 | ## Signatures and Explicit Typing
228 |
229 | Function signatures are useful for quickly learning the input and output of functions. The last type is the return type and all preceding types are the input types.
230 |
231 | ```fsharp
232 | int -> string // this defines a function that receives an integer; returns a string
233 | int -> int -> string // two integer inputs; returns a string
234 | unit -> string // unit; returns a string
235 | string -> unit // accepts a string; no return
236 | (int * string) -> string -> string // a tuple of int and string, and a string inputs; returns a string
237 | ```
238 |
239 | Most of the time, the compiler can determine the type of a parameter, but there are cases may you wish to be explicit or the compiler needs a hand.
240 | Here is a function with a signature `string -> char -> int` and the input and return types are explicit:
241 |
242 | ```fsharp
243 | let countWordsStartingWithLetter (theString: string) (theLetter: char) : int =
244 | theString.Split ' '
245 | |> Seq.where (fun (word: string) -> word.StartsWith theLetter) // explicit typing in a lambda
246 | |> Seq.length
247 | ```
248 |
249 | Examples of functions that take [`unit`](#functions-unit-type) as arguments and return different [Collection](#collections) types.
250 |
251 | ```fsharp
252 | let getList (): int list = ... // unit -> int list
253 | let getArray (): int[] = ...
254 | let getSeq (): seq = ...
255 | ```
256 |
257 | A complex declaration with an [Anonymous Record](#data-types-anonymous-records):
258 |
259 | ```fsharp
260 | let anonRecordFunc (record: {| Count: int; LeftAndRight: bigint * bigint |}) =
261 | ...
262 | ```
263 |
264 |
265 |
266 | ## Recursive Functions
267 |
268 | The `rec` keyword is used together with the `let` keyword to define a recursive function:
269 |
270 | ```fsharp
271 | let rec fact x =
272 | if x < 1 then 1
273 | else x * fact (x - 1)
274 | ```
275 |
276 | ### TailCallAttribute
277 |
278 | In _tail recursive_ functions, the recursive call is the final operation in the function, with its result directly returned without a nested function call (and the stack usage that implies). This pattern allows the compiler to instead generate a loop equivalent of the nested invocation by reusing the current stack frame instead of allocating a new one for each call.
279 |
280 | As a guardrail, you can use the "TailCall" attribute (since F# 8).
281 |
282 | By default, the compiler will emit a warning if this attribute is used with a function that is not properly tail recursive. It is typically a good idea to elevate this warning to an error, either in your project file, or by using a [compiler option](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/compiler-messages/).
283 |
284 | If we add this attribute to the previous example:
285 |
286 | ```fsharp
287 | []
288 | let rec fact x =
289 | if x < 1 then 1
290 | else x * fact (x - 1)
291 | ```
292 | ...the compiler gives us this warning:
293 | ```
294 | Warning FS3569 : The member or function 'fact' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.
295 | ```
296 | However, when refactored to be properly tail recursive by using an accumulator parameter, the warning goes away:
297 | ```fsharp
298 | []
299 | let rec factTail acc x =
300 | if x < 1 then acc
301 | else factTail (acc * x) (x - 1)
302 | ```
303 |
304 | ### Mutually Recursive Functions
305 |
306 | Pairs or groups of functions that call each other are indicated by both `rec` and `and` keywords:
307 |
308 | ```fsharp
309 | let rec even x =
310 | if x = 0 then true
311 | else odd (x - 1)
312 |
313 | and odd x =
314 | if x = 0 then false
315 | else even (x - 1)
316 | ```
317 |
318 |
319 |
320 |
321 |
322 | ## Statically Resolved Type Parameters
323 |
324 | 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.
325 |
326 | ```fsharp
327 | let inline add x y = x + y
328 | let integerAdd = add 1 2
329 | let floatAdd = add 1.0f 2.0f // without `inline` on `add` function, this would cause a type error
330 | ```
331 |
332 | ```fsharp
333 | type RequestA = { Id: string; StringValue: string }
334 | type RequestB = { Id: string; IntValue: int }
335 |
336 | let requestA: RequestA = { Id = "A"; StringValue = "Value" }
337 | let requestB: RequestB = { Id = "B"; IntValue = 42 }
338 |
339 | let inline getId<'T when 'T : (member Id: string)> (x: 'T) = x.Id
340 |
341 | let idA = getId requestA // "A"
342 | let idB = getId requestB // "B"
343 | ```
344 |
345 | 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.
346 |
347 |
348 |
349 | # Collections
350 |
351 |
352 |
353 | ## Lists
354 |
355 | A *list* is an immutable collection of elements of the same type. Implemented internally as a linked list.
356 |
357 | ```fsharp
358 | // Create
359 | let list1 = [ "a"; "b" ]
360 | let list2 =
361 | [ 1
362 | 2 ]
363 | let list3 = "c" :: list1 // prepending; [ "c"; "a"; "b" ]
364 | let list4 = list1 @ list3 // concat; [ "a"; "b"; "c"; "a"; "b" ]
365 | let list5 = [ 1..2..9 ] // start..increment..last; [ 1; 3; 5; 7; 9 ]
366 |
367 | // Slicing is inclusive
368 | let firstTwo = list5[0..1] // [ 1; 3 ]
369 |
370 | // Pattern matching
371 | match myList with
372 | | [] -> ... // empty list
373 | | [ 3 ] -> ... // a single item, which is '3'
374 | | [ _; 4 ] -> ... // two items, second item is '4'
375 | | head :: tail -> ... // cons pattern; matches non-empty. `head` is the first item, `tail` is the rest
376 |
377 | // Tail-recursion with a list, using cons pattern
378 | let sumEachItem (myList:int list) =
379 | match myList with
380 | | [] -> 0
381 | | head :: tail -> head + sumEachItem tail
382 | ```
383 |
384 | See the [List Module](https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-listmodule.html) for built-in functions.
385 |
386 |
387 |
388 | ## Arrays
389 |
390 | *Arrays* are fixed-size, zero-based, collections of consecutive data elements maintained as one block of memory. They are *mutable*; individual elements can be changed.
391 |
392 |
393 | ```fsharp
394 | // Create
395 | let array1 = [| "a"; "b"; "c" |]
396 | let array2 =
397 | [| 1
398 | 2 |]
399 | let array3 = [| 1..2..9 |] // start..increment..last; [| 1; 3; 5; 7; 9 |]
400 |
401 | // Indexed access
402 | let first = array1[0] // "a"
403 |
404 | // Slicing is inclusive; [| "a"; "b" |]
405 | let firstTwo = array1[0..1]
406 |
407 | // Assignment using `<-`
408 | array1[1] <- "d" // [| "a"; "d"; "c" |]
409 |
410 | // Pattern matching
411 | match myArray with
412 | | [||] -> ... // match an empty array
413 | | [| 3 |] -> ... // match array with single 3 item
414 | | [| _; 4 |] -> ... // match array with 2 items, second item = 4
415 | ```
416 |
417 | See the [Array Module](https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-arraymodule.html) for built-in functions.
418 |
419 |
420 |
421 | ## Sequences
422 |
423 | A *sequence* is a logical series of elements of the same type. `seq<'t>` is an alias for [`System.Collections.Generic.IEnumerable<'t>`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.ienumerable-1).
424 |
425 | ```fsharp
426 | // Create
427 | let seq1 = { 1; 2 }
428 | let seq2 = seq {
429 | 1
430 | 2 }
431 | let seq3 = seq { 1..2..9 } // start..increment..last; 1,3,5,7,9
432 | ```
433 |
434 | See the [Seq Module](https://fsharp.github.io/fsharp-core-docs/reference/fsharp-collections-seqmodule.html) for built-in functions.
435 |
436 | ## Collection comprehension
437 |
438 | - Computed expressions with `->`. Results in _1, 3, 5, 7, 9_
439 | ```fsharp
440 | let listComp = [ for i in 0..4 -> 2 * i + 1 ]
441 | let arrayComp = [| for i in 0..4 -> 2 * i + 1 |]
442 | let seqComp = seq { for i in 0..4 -> 2 * i + 1 }
443 | ```
444 |
445 | - Using computed expressions with `yield` and `yield!`. (`yield` is optional in a `do`, but is being used explicitly here):
446 | ```fsharp
447 | let comprehendedList = [ // [ 1;3;5;7;9 ]
448 | for i in 0..4 do
449 | yield 2 * i + 1
450 | ]
451 | let comprehendedArray = [| // [| 1;3;5;7;9;1;3;5;7;9 |]
452 | for i in 0..4 do
453 | yield 2 * i + 1
454 | yield! comprehendedList
455 | |]
456 | let comprehendedSequence = seq { // seq { 1;3;5;7;9;1;3;5;7;9;.... }
457 | while true do
458 | yield! listWithYield
459 | }
460 | ```
461 |
462 |
463 |
464 | # Data Types
465 |
466 |
467 |
468 | ## Tuples
469 |
470 | A *tuple* is a grouping of unnamed but ordered values, possibly of different types:
471 |
472 | ```fsharp
473 | // Construction
474 | let numberAndWord = (1, "Hello")
475 | let numberAndWordAndNow = (1, "Hello", System.DateTime.Now)
476 |
477 | // Deconstruction
478 | let (number, word) = numberAndWord
479 | let (_, _, now) = numberAndWordAndNow
480 |
481 | // fst and snd functions for two-item tuples:
482 | let number = fst numberAndWord
483 | let word = snd numberAndWord
484 |
485 | // Pattern matching
486 | let printNumberAndWord numberAndWord =
487 | match numberAndWord with
488 | | (1, word) -> printfn $"One: %s{word}"
489 | | (2, word) -> printfn $"Two: %s{word}"
490 | | (_, word) -> printfn $"Number: %s{word}"
491 |
492 | // Function parameter deconstruction
493 | let printNumberAndWord' (number, word) = printfn $"%d{number}: %s{word}"
494 | ```
495 |
496 | In C#, if a method has an `out` parameter (e.g. [`DateTime.TryParse`](https://learn.microsoft.com/en-us/dotnet/api/system.datetime.tryparse)) the `out` result will be part of a tuple.
497 |
498 | ```fsharp
499 | let (success, outParsedDateTime) = System.DateTime.TryParse("2001/02/06")
500 | ```
501 |
502 | See [Tuples (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/tuples) for learn more.
503 |
504 |
505 |
506 | ## Records
507 |
508 | *Records* represent aggregates of named values. They are sealed classes with extra toppings: default immutability, structural equality, and pattern matching support.
509 |
510 | ```fsharp
511 | // Declare
512 | type Person = { Name: string; Age: int }
513 | type Car =
514 | { Make: string
515 | Model: string
516 | Year: int }
517 |
518 | // Create
519 | let paul = { Name = "Paul"; Age = 28 }
520 |
521 | // Copy and Update
522 | let paulsTwin = { paul with Name = "Jim" }
523 |
524 | // Built-in equality
525 | let evilPaul = { Name = "Paul"; Age = 28 }
526 | paul = evilPaul // true
527 |
528 | // Pattern matching
529 | let isPaul person =
530 | match person with
531 | | { Name = "Paul" } -> true
532 | | _ -> false
533 | ```
534 |
535 | See [Records (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/records) to learn more; including `struct`-based records.
536 |
537 |
538 |
539 | ## Anonymous Records
540 |
541 | *Anonymous Records* represent aggregates of named values, but do not need declaring before use.
542 |
543 | ```fsharp
544 | // Create
545 | let anonRecord1 = {| Name = "Don Syme"; Language = "F#"; Age = 999 |}
546 |
547 | // Copy and Update
548 | let anonRecord2 = {| anonRecord1 with Name = "Mads Torgersen"; Language = "C#" |}
549 |
550 | let getCircleStats (radius: float) =
551 | {| Radius = radius
552 | Diameter = radius * 2.0
553 | Area = System.Math.PI * (radius ** 2.0)
554 | Circumference = 2.0 * System.Math.PI * radius |}
555 |
556 | // Signature
557 | let printCircleStats (circle: {| Radius: float; Area: float; Circumference: float; Diameter: float |}) =
558 | printfn $"Circle with R=%f{circle.Radius}; D=%f{circle.Diameter}; A=%f{circle.Area}; C=%f{circle.Circumference}"
559 |
560 | let cc = getCircleStats 2.0
561 | printCircleStats cc
562 | ```
563 |
564 | See [Anonymous Records (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/anonymous-records) to learn more; including `struct`-based anonymous records.
565 |
566 |
567 |
568 | ## Discriminated Unions
569 |
570 | *Discriminated unions* (DU) provide support for values that can be one of a number of named cases, each possibly with different values and types.
571 |
572 | ```fsharp
573 | // Declaration
574 | type Interaction =
575 | | Keyboard of char
576 | | KeyboardWithModifier of char * modifier: System.ConsoleModifiers
577 | | MouseClick of countOfClicks: int
578 |
579 | // Create
580 | let interaction1 = MouseClick 1
581 | let interaction2 = MouseClick (countOfClicks = 2)
582 | let interaction3 = KeyboardWithModifier ('c', System.ConsoleModifiers.Control)
583 |
584 | // Pattern matching
585 | match interaction3 with
586 | | Keyboard chr -> $"Character: {chr}"
587 | | KeyboardWithModifier (chr, modifier) -> $"Character: {modifier}+{chr}"
588 | | MouseClick (countOfClicks = 1) -> "Click"
589 | | MouseClick (countOfClicks = x) -> $"Clicked: {x}"
590 | ```
591 |
592 | Generics
593 |
594 | ```fsharp
595 | type Tree<'T> =
596 | | Node of Tree<'T> * 'T * Tree<'T>
597 | | Leaf
598 |
599 | let rec depth =
600 | match depth with
601 | | Node (l, _, r) -> 1 + max (depth l) (depth r)
602 | | Leaf -> 0
603 | ```
604 |
605 | 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).
606 |
607 | ```fsharp
608 | let optionPatternMatch input =
609 | match input with
610 | | Some value -> printfn $"input is %d{value}"
611 | | None -> printfn "input is missing"
612 |
613 | let resultPatternMatch input =
614 | match input with
615 | | Ok value -> $"Input: %d{value}"
616 | | Error value -> $"Error: %d{value}"
617 | ```
618 |
619 | Single-case discriminated unions are often used to create type-safe abstractions with pattern matching support:
620 |
621 | ```fsharp
622 | type OrderId = Order of string
623 |
624 | // Create a DU value
625 | let orderId = Order "12"
626 |
627 | // Use pattern matching to deconstruct single-case DU
628 | let (Order id) = orderId // id = "12"
629 | ```
630 |
631 | See [Discriminated Unions](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/discriminated-unions) to learn more.
632 |
633 |
634 |
635 | # Pattern Matching
636 |
637 | Patterns are a core concept that makes the F# language and other MLs very powerful.
638 | They are found in `let` bindings, `match` expressions, lambda expressions, and [exceptions](#exceptions).
639 |
640 | The matches are evaluated top-to-bottom, left-to-right; and the first one to match is selected.
641 |
642 | Examples of pattern matching in [Collections](#collections) and [Data Types](#data-types) can be found in their corresponding sections.
643 | Here are some additional patterns:
644 |
645 | ```fsharp
646 | match intValue with
647 | | 0 -> "Zero" // constant pattern
648 | | 1 | 2 -> "One or Two" // OR pattern with constants
649 | | x -> $"Something else: {x}" // variable pattern; assign value to x
650 |
651 | match tupleValue with
652 | | (_ ,3) & (x, y) -> $"{x}, 3" // AND pattern with a constant and variable; matches 3 and assign 3 to x
653 | | _ -> "Wildcard" // underscore matches anything
654 | ```
655 |
656 | ## `when` Guard clauses
657 |
658 | In order to match sophisticated inputs, one can use `when` to create filters, or guards, on patterns:
659 |
660 | ```fsharp
661 | match num with
662 | | 0 -> 0
663 | | x when x < 0 -> -1
664 | | x -> 1
665 | ```
666 |
667 | ## Pattern matching `function`
668 |
669 | The `let..match..with` statement can be simplified using just the `function` statement:
670 |
671 | ```fsharp
672 | let filterNumbers num =
673 | match num with
674 | | 1 | 2 | 3 -> printfn "Found 1, 2, or 3!"
675 | | a -> printfn "%d" a
676 |
677 | let filterNumbers' = // the parameter and `match num with` are combined
678 | function | 1 | 2 | 3 -> printfn "Found 1, 2, or 3!"
679 | | a -> printfn "%d" a
680 | ```
681 |
682 | See [Pattern Matching (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/pattern-matching) to learn more.
683 |
684 |
685 |
686 | # Exceptions
687 |
688 | ## Try..With
689 |
690 | An illustrative example with: custom F# exception creation, all exception aliases, `raise()` usage, and an exhaustive demonstration of the exception handler patterns:
691 |
692 | ```fsharp
693 | open System
694 | exception MyException of int * string // (1)
695 | let guard = true
696 |
697 | try
698 | failwith "Message" // throws a System.Exception (aka exn)
699 | nullArg "ArgumentName" // throws a System.ArgumentNullException
700 | invalidArg "ArgumentName" "Message" // throws a System.ArgumentException
701 | invalidOp "Message" // throws a System.InvalidOperation
702 |
703 | raise(NotImplementedException("Message")) // throws a .NET exception (2)
704 | raise(MyException(0, "Message")) // throws an F# exception (2)
705 |
706 | true // (3)
707 | with
708 | | :? ArgumentNullException -> printfn "NullException"; false // (3)
709 | | :? ArgumentException as ex -> printfn $"{ex.Message}"; false // (4)
710 | | :? InvalidOperationException as ex when guard -> printfn $"{ex.Message}"; reraise() // (5,6)
711 | | MyException(num, str) when guard -> printfn $"{num}, {str}"; false // (5)
712 | | MyException(num, str) -> printfn $"{num}, {str}"; reraise() // (6)
713 | | ex when guard -> printfn $"{ex.Message}"; false
714 | | ex -> printfn $"{ex.Message}"; false
715 | ```
716 |
717 | 1. define your own F# exception types with `exception`, a new type that will inherit from `System.Exception`;
718 | 2. use `raise()` to throw an F# or .NET exception;
719 | 3. the entire `try..with` expression must evaluate to the same type, in this example: bool;
720 | 4. `ArgumentNullException` inherits from `ArgumentException`, so `ArgumentException` must follow after;
721 | 5. support for `when` guards;
722 | 6. use `reraise()` to re-throw an exception; works with both .NET and F# exceptions
723 |
724 | The difference between F# and .NET exceptions is how they are created and how they can be handled.
725 |
726 | ## Try..Finally
727 |
728 | The `try..finally` expression enables you to execute clean-up code even if a block of code throws an exception. Here's an example that also defines custom exceptions.
729 |
730 | ```fsharp
731 | exception InnerError of string
732 | exception OuterError of string
733 |
734 | let handleErrors x y =
735 | try
736 | try
737 | if x = y then raise (InnerError("inner"))
738 | else raise (OuterError("outer"))
739 | with
740 | | InnerError str -> printfn "Error1 %s" str
741 | finally
742 | printfn "Always print this."
743 | ```
744 |
745 | Note that `finally` does not follow `with`. `try..with` and `try..finally` are separate expressions.
746 |
747 |
748 |
749 | # Classes and Inheritance
750 |
751 | This example is a basic class with (1) local let bindings, (2) properties, (3) methods, and (4) static members.
752 |
753 | ```fsharp
754 | type Vector(x: float, y: float) =
755 | let mag = sqrt(x * x + y * y) // (1)
756 | member _.X = x // (2)
757 | member _.Y = y
758 | member _.Mag = mag
759 | member _.Scale(s) = // (3)
760 | Vector(x * s, y * s)
761 | static member (+) (a : Vector, b : Vector) = // (4)
762 | Vector(a.X + b.X, a.Y + b.Y)
763 | ```
764 |
765 | Call a base class from a derived one.
766 |
767 | ```fsharp
768 | type Animal() =
769 | member _.Rest() = ()
770 |
771 | type Dog() =
772 | inherit Animal()
773 | member _.Run() =
774 | base.Rest()
775 | ```
776 |
777 | *Upcasting* is denoted by `:>` operator.
778 |
779 | ```fsharp
780 | let dog = Dog()
781 | let animal = dog :> Animal
782 | ```
783 |
784 | *Dynamic downcasting* (`:?>`) might throw an `InvalidCastException` if the cast doesn't succeed at runtime.
785 |
786 | ```fsharp
787 | let shouldBeADog = animal :?> Dog
788 | ```
789 |
790 |
791 |
792 | # Interfaces and Object Expressions
793 |
794 | Declare `IVector` interface and implement it in `Vector`.
795 |
796 | ```fsharp
797 | type IVector =
798 | abstract Scale : float -> IVector
799 |
800 | type Vector(x, y) =
801 | interface IVector with
802 | member _.Scale(s) =
803 | Vector(x * s, y * s) :> IVector
804 | member _.X = x
805 | member _.Y = y
806 | ```
807 |
808 | Another way of implementing interfaces is to use *object expressions*.
809 |
810 | ```fsharp
811 | type ICustomer =
812 | abstract Name : string
813 | abstract Age : int
814 |
815 | let createCustomer name age =
816 | { new ICustomer with
817 | member _.Name = name
818 | member _.Age = age }
819 | ```
820 |
821 |
822 |
823 | # Active Patterns
824 |
825 | ## Single-case active patterns
826 |
827 | *Single-case active patterns* can be thought of as a simple way to convert data to a new form.
828 |
829 | ```fsharp
830 | // Basic
831 | let (|EmailDomain|) email =
832 | let match' = Regex.Match(email, "@(.*)$")
833 | if match'.Success
834 | then match'.Groups[1].ToString()
835 | else ""
836 | let (EmailDomain emailDomain) = "yennefer@aretuza.org" // emailDomain = 'aretuza.org'
837 |
838 | // As Parameters
839 | open System.Numerics
840 | let (|Real|) (x: Complex) =
841 | (x.Real, x.Imaginary)
842 | let addReal (Real (real1, _)) (Real (real2, _)) = // conversion done in the parameters
843 | real1 + real2
844 | let addRealOut = addReal Complex.ImaginaryOne Complex.ImaginaryOne
845 |
846 | // Parameterized
847 | let (|Default|) onNone value =
848 | match value with
849 | | None -> onNone
850 | | Some e -> e
851 | let (Default "random citizen" name) = None // name = "random citizen"
852 | let (Default "random citizen" name) = Some "Steve" // name = "Steve"
853 | ```
854 |
855 | ## Complete active patterns
856 |
857 | ```fsharp
858 | let (|Even|Odd|) i =
859 | if i % 2 = 0 then Even else Odd
860 |
861 | let testNumber i =
862 | match i with
863 | | Even -> printfn "%d is even" i
864 | | Odd -> printfn "%d is odd" i
865 |
866 | let (|Phone|Email|) (s:string) =
867 | if s.Contains '@' then Email $"Email: {s}" else Phone $"Phone: {s}"
868 |
869 | match "yennefer@aretuza.org" with // output: "Email: yennefer@aretuza.org"
870 | | Email email -> printfn $"{email}"
871 | | Phone phone -> printfn $"{phone}"
872 | ```
873 |
874 | ## Partial active patterns
875 |
876 | *Partial active patterns* share the syntax of parameterized patterns, but their active recognizers accept only one argument.
877 | A *Partial active pattern* must return an `Option<'T>`.
878 |
879 | ```fsharp
880 | let (|DivisibleBy|_|) by n =
881 | if n % by = 0
882 | then Some DivisibleBy
883 | else None
884 |
885 | let fizzBuzz = function
886 | | DivisibleBy 3 & DivisibleBy 5 -> "FizzBuzz"
887 | | DivisibleBy 3 -> "Fizz"
888 | | DivisibleBy 5 -> "Buzz"
889 | | i -> string i
890 | ```
891 |
892 |
893 |
894 | # Asynchronous Programming
895 |
896 | F# asynchronous programming support consists of two complementary mechanisms::
897 | - .NET's Tasks (via `task { }` expressions). This provides semantics very close to that of C#'s `async`/`await` mechanism, requiring explicit direct management of `CancellationToken`s.
898 | - F# native `Async` computations (via `async { }` expressions). Predates `Task`. Provides intrinsic `CancellationToken` propagation.
899 |
900 | ## .NET Tasks
901 |
902 | In F#, .NET Tasks can be constructed using the `task { }` computational expression.
903 | .NET Tasks are "hot" - they immediately start running. At the first `let!` or `do!`, the `Task<'T>` is returned and execution continues on the ThreadPool.
904 |
905 | ```fsharp
906 | open System
907 | open System.Threading
908 | open System.Threading.Tasks
909 | open System.IO
910 |
911 | let readFile filename ct = task {
912 | printfn "Started Reading Task"
913 | do! Task.Delay((TimeSpan.FromSeconds 5), cancellationToken = ct) // use do! when awaiting a Task
914 | let! text = File.ReadAllTextAsync(filename, ct) // use let! when awaiting a Task<'T>, and unwrap 'T from Task<'T>.
915 | return text
916 | }
917 |
918 | let readFileTask: Task = readFile "myfile.txt" CancellationToken.None // (before return) Output: Started Reading Task
919 |
920 | // (readFileTask continues execution on the ThreadPool)
921 |
922 | let fileContent = readFileTask.Result // Blocks thread and waits for content. (1)
923 | let fileContent' = readFileTask.Result // Task is already completed, returns same value immediately; no output
924 | ```
925 |
926 | (1) `.Result` used for demonstration only. Read about [async/await Best Practices](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#async-all-the-way)
927 |
928 | ## Async Computations
929 |
930 | Async computations were invented before .NET Tasks existed, which is why F# has two core methods for asynchronous programming. However, async computations did not become obsolete. They offer another, but different, approach: dataflow.
931 | Async computations are constructed using `async { }` expressions, and the [`Async` module](https://fsharp.github.io/fsharp-core-docs/reference/fsharp-control-fsharpasync.html#section3) is used to compose and execute them.
932 | In contrast to .NET Tasks, async expressions are "cold" (need to be explicitly started) and every execution [propagates a CancellationToken implicitly](#asynchronous-programming-cancellation-async).
933 |
934 | ```fsharp
935 | open System
936 | open System.Threading
937 | open System.IO
938 |
939 | let readFile filename = async {
940 | do! Async.Sleep(TimeSpan.FromSeconds 5) // use do! when awaiting an Async
941 | let! text = File.ReadAllTextAsync(filename) |> Async.AwaitTask // (1)
942 | printfn "Finished Reading File"
943 | return text
944 | }
945 |
946 | // compose a new async computation from exising async computations
947 | let readFiles = [ readFile "A"; readFile "B" ] |> Async.Parallel
948 |
949 | // execute async computation
950 | let textOfFiles: string[] = readFiles |> Async.RunSynchronously
951 | // Out: Finished Reading File
952 | // Out: Finished Reading File
953 |
954 | // re-execute async computation again
955 | let textOfFiles': string[] = readFiles |> Async.RunSynchronously
956 | // Out: Finished Reading File
957 | // Out: Finished Reading File
958 | ```
959 |
960 | (1) As .NET Tasks became the central component of task-based asynchronous programming after F# Async were introduced, F#'s Async has `Async.AwaitTask` to map from `Task<'T>` to `Async<'T>`. Note that cancellation and exception handling require [special considerations](https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/141).
961 |
962 | ### Creation / Composition
963 |
964 | The `Async` module has a number of functions to compose and start computations. The full list with explanations can be found in the [Async Type Reference](https://fsharp.github.io/fsharp-core-docs/reference/fsharp-control-fsharpasync.html#section0).
965 |
966 | | Function | Description |
967 | |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
968 | | Async.Ignore | Creates an `Async` computation from an `Async<'T>` |
969 | | Async.Parallel | Composes a new computation from multiple computations, `Async<'T> seq`, and runs them in parallel; it returns all the results in an array `Async<'T[]>` |
970 | | Async.Sequential | Composes a new computation from multiple computations, `Async<'T> seq`, and runs them in series; it returns all the results in an array `Async<'T[]>` |
971 | | Async.Choice | Composes a new computation from multiple computations, `Async<'T option> seq`, and returns the first where `'T'` is `Some value` (all others running are canceled). If all computations return `None` then the result is `None` |
972 |
973 | For all functions that compose a new computation from children, if any child computations raise an exception, then the overall computation will trigger an exception. The `CancellationToken` passed to the child computations will be triggered, and execution continues when all running children have cancelled execution.
974 |
975 | ### Executing
976 |
977 | | Function | Description |
978 | |------------------------------|------------------------------------------------------------------------------------------------------------------------------|
979 | | Async.RunSynchronously | Runs an async computation and awaits its result. |
980 | | Async.StartAsTask | Runs an async computation on the ThreadPool and wraps the result in a `Task<'T>`. |
981 | | Async.StartImmediateAsTask | Runs an async computation, starting immediately on the current operating system thread, and wraps the result in a `Task<'T>` |
982 | | Async.Start | Runs an `Async` computation on the ThreadPool (without observing any exceptions). |
983 | | Async.StartImmediate | Runs a computation, starting immediately on the current thread and continuations completing in the ThreadPool. |
984 |
985 | ## Cancellation
986 |
987 | ### .NET Tasks
988 |
989 | .NET Tasks do not have any intrinsic handling of `CancellationToken`s; you are responsible for passing `CancellationToken`s down the call hierarchy to all sub-Tasks.
990 |
991 | ```fsharp
992 | open System
993 | open System.Threading
994 | open System.Threading.Tasks
995 |
996 | let loop (token: CancellationToken) = task {
997 | for cnt in [ 0 .. 9 ] do
998 | printf $"{cnt}: And..."
999 | do! Task.Delay((TimeSpan.FromSeconds 2), token) // token is required for Task.Delay to be interruptible
1000 | printfn "Done"
1001 | }
1002 |
1003 | let cts = new CancellationTokenSource (TimeSpan.FromSeconds 5)
1004 | let runningLoop = loop cts.Token
1005 | try
1006 | runningLoop.GetAwaiter().GetResult() // (1)
1007 | with :? OperationCanceledException -> printfn "Canceled"
1008 | ```
1009 |
1010 | Output:
1011 |
1012 | 0: And...Done
1013 | 1: And...Done
1014 | 2: And...Canceled
1015 |
1016 | (1) `.GetAwaiter().GetResult()` used for demonstration only. Read about [async/await Best Practices](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#async-all-the-way)
1017 |
1018 |
1019 |
1020 | ### Async
1021 |
1022 | Asynchronous computations have the benefit of implicit `CancellationToken` passing and checking.
1023 |
1024 | ```fsharp
1025 | open System
1026 | open System.Threading
1027 | open System.Threading.Tasks
1028 |
1029 | let loop = async {
1030 | for cnt in [ 0 .. 9 ] do
1031 | printf $"{cnt}: And..."
1032 | do! Async.Sleep(TimeSpan.FromSeconds 1) // Async.Sleep implicitly receives and checks `cts.Token`
1033 |
1034 | let! ct = Async.CancellationToken // when interoperating with Tasks, cancellationTokens need to be passed explicitly
1035 | do! Task.Delay((TimeSpan.FromSeconds 1), cancellationToken = ct) |> Async.AwaitTask
1036 |
1037 | printfn "Done"
1038 | }
1039 |
1040 | let cts = new CancellationTokenSource(TimeSpan.FromSeconds 5)
1041 | try
1042 | Async.RunSynchronously (loop, Timeout.Infinite, cts.Token)
1043 | with :? OperationCanceledException -> printfn "Canceled"
1044 | ```
1045 |
1046 | Output:
1047 |
1048 | 0: And...Done
1049 | 1: And...Done
1050 | 2: And...Canceled
1051 |
1052 | All methods for cancellation can be found in the [Core Library Documentation](https://fsharp.github.io/fsharp-core-docs/reference/fsharp-control-fsharpasync.html#section3)
1053 |
1054 | ## More to Explore
1055 |
1056 | Asynchronous programming is a vast topic. Here are some other resources worth exploring:
1057 |
1058 | - [Asynchronous Programming in F#](https://learn.microsoft.com/en-us/dotnet/fsharp/tutorials/async) - Microsoft's tutorial guide. Recommended as it is up-to-date and expands on some of the topics here.
1059 | - [Iced Tasks](https://github.com/TheAngryByrd/IcedTasks?tab=readme-ov-file#icedtasks) - .NET Tasks start immediately. The IcedTasks library provide additional [computational expressions](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions) such as `cancellableTask`, which combines the benefits of .NET Tasks (natural interoperation with Task APIs and the performance benefits of the `task`'s State-Machine based implementation) with asynchronous expressions (composability, implicit `CancellationToken` passing, and the fact that you can invoke (or retry) a given computation multiple times).
1060 | - [Asynchronous Programming Best Practices](https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#table-of-contents) by David Fowler - offers a fantastic list of good practices for .NET Task usage.
1061 |
1062 |
1063 |
1064 | # Code Organization
1065 |
1066 |
1067 |
1068 | ## Modules
1069 | Modules are key building blocks for grouping related code; they can contain `types`, `let` bindings, or (nested) sub `module`s.
1070 | Identifiers within modules can be referenced using dot notation, or you can bring them into scope via the [`open`](#open-and-autoopen) keyword.
1071 | Illustrative-only example:
1072 |
1073 | ```fsharp
1074 | module Money =
1075 | type CardInfo =
1076 | { number: string
1077 | expiration: int * int }
1078 |
1079 | type Payment =
1080 | | Card of CardInfo
1081 | | Cash of int
1082 |
1083 | module Functions =
1084 | let validCard (cardNumber: string) =
1085 | cardNumber.Length = 16 && (cardNumber[0], ['3';'4';'5';'6']) ||> List.contains
1086 | ```
1087 |
1088 | If there is only one module in a file, the `module` name can be declared at the top, and all code constructs
1089 | within the file will be included in the `module`s definition (no indentation required).
1090 |
1091 | ```fsharp
1092 | module Functions // notice there is no '=' when at the top of a file
1093 |
1094 | let sumOfSquares n = seq {1..n} |> Seq.sumBy (fun x -> x * x) // Functions.sumOfSquares
1095 | ```
1096 |
1097 | ## Namespaces
1098 | Namespaces are simply dotted names that prefix `type` and `module` declarations to allow for hierarchical scoping.
1099 | The first `namespace` directives must be placed at the top of the file. Subsequent `namespace` directives either: (a) create a sub-namespace; or (b) create a new namespace.
1100 |
1101 | ```fsharp
1102 | namespace MyNamespace
1103 |
1104 | module MyModule = // MyNamspace.MyModule
1105 | let myLet = ... // MyNamspace.MyModule.myLet
1106 |
1107 | namespace MyNamespace.SubNamespace
1108 |
1109 | namespace MyNewNamespace // a new namespace
1110 | ```
1111 |
1112 | A top-level [`module`](#modules)'s namespace can be specified via a dotted prefix:
1113 |
1114 | ```fsharp
1115 | module MyNamespace.SubNamespace.Functions
1116 | ```
1117 |
1118 |
1119 |
1120 | ## Open and AutoOpen
1121 | The `open` keyword can be used on `module`, `namespace`, and `type`.
1122 |
1123 | ```fsharp
1124 | module Groceries =
1125 | type Fruit =
1126 | | Apple
1127 | | Banana
1128 |
1129 | let fruit1 = Groceries.Banana
1130 | open Groceries // module
1131 | let fruit2 = Apple
1132 | ```
1133 | ```fsharp
1134 | open System.Diagnostics // namespace
1135 | let stopwatch = Stopwatch.StartNew() // Stopwatch is accessible
1136 | ```
1137 | ```fsharp
1138 | open type System.Text.RegularExpressions.Regex // type
1139 | let isHttp url = IsMatch("^https?:", url) // Regex.IsMatch directly accessible
1140 | ```
1141 |
1142 | Available to `module` declarations only, is the `AutoOpen` attribute, which alleviates the need for an `open`.
1143 |
1144 | ```fsharp
1145 | []
1146 | module Groceries =
1147 | type Fruit =
1148 | | Apple
1149 | | Banana
1150 |
1151 | let fruit = Banana
1152 | ```
1153 |
1154 | *However*, `AutoOpen` should be used cautiously. When an `open` or `AutoOpen` is used, all declarations in the containing element
1155 | will be brought into scope. This can lead to [shadowing](https://en.wikipedia.org/wiki/Variable_shadowing); where the last
1156 | named declaration replaces all prior identically-named declarations. There is *no* error - or even a warning - in F#, when shadowing occurs.
1157 | A [coding convention (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/style-guide/conventions#sort-open-statements-topologically) exists for `open`
1158 | statements to avoid pitfalls; `AutoOpen` would sidestep this.
1159 |
1160 | ## Accessibility Modifiers
1161 |
1162 | F# supports `public`, `private` (limiting access to its containing `type` or `module`) and `internal` (limiting access to its containing assembly).
1163 | They can be applied to `module`, `let`, `member`, `type`, [`new` (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/classes#constructors), and [`val` (MS Learn)](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/members/explicit-fields-the-val-keyword).
1164 |
1165 | With the exception of `let` bindings in a class `type`, everything defaults to `public`.
1166 |
1167 | | Element | Example with Modifier |
1168 | |-------------------------------------------------|--------------------------------------------|
1169 | | Module | `module internal MyModule =` |
1170 | | Module .. `let` | `let private value =` |
1171 | | Record | `type internal MyRecord = { id: int }` |
1172 | | Record [ctor](#smart-constructors) | `type MyRecord = private { id: int }` |
1173 | | Discriminated Union | `type internal MyDiscUni = A \| B` |
1174 | | Discriminated Union [ctor](#smart-constructors) | `type MyDiscUni = private A \| B ` |
1175 | | Class | `type internal MyClass() =` |
1176 | | Class [ctor](#smart-constructors) | `type MyClass private () =` |
1177 | | Class Additional [ctor](#smart-constructors) | `internal new() = MyClass("defaultValue")` |
1178 | | Class .. `let` | *Always private. Cannot be overridden* |
1179 | | `type` .. `member` | `member private _.TypeMember =` |
1180 | | `type` .. `val` | `val internal explicitInt : int` |
1181 |
1182 |
1183 |
1184 | ## Smart Constructors
1185 |
1186 | Making a primary constructor (ctor) `private` or `internal` is a common convention for ensuring value integrity;
1187 | otherwise known as ["making illegal states unrepresentable" (YouTube:Effective ML)](https://youtu.be/-J8YyfrSwTk?si=ml3AWro6jG77F0YW&t=1080).
1188 |
1189 | Example of Single-case Discriminated Union with a `private` constructor that constrains a quantity between 0 and 100:
1190 |
1191 | ```fsharp
1192 | type UnitQuantity =
1193 | private UnitQuantity of int
1194 |
1195 | module UnitQuantity = // common idiom: type companion module
1196 | let tryCreate qty =
1197 | if qty < 1 || qty > 100
1198 | then None
1199 | else Some (UnitQuantity qty)
1200 | let value (UnitQuantity uQty) = uQty
1201 | let zero = UnitQuantity 0
1202 | ...
1203 | let unitQtyOpt = UnitQuantity.tryCreate 5
1204 |
1205 | let validQty =
1206 | unitQtyOpt
1207 | |> Option.defaultValue UnitQuantity.zero
1208 | ```
1209 |
1210 | ## Recursive Reference
1211 |
1212 | F#'s type inference and name resolution runs in file and line order. By default, any forward references are considered errors.
1213 | This default provides a single benefit, which can be hard to appreciate initially: you never need to look beyond the current file for a dependency.
1214 | In general this also nudges toward more careful design and organisation of codebases,
1215 | which results in cleaner, maintainable code. However, in rare cases forward referencing might be needed.
1216 | To do this we have `rec` for `module` and `namespace`; and `and` for `type` and [`let` (Recursive Functions)](#functions-recursive) functions.
1217 |
1218 | ```fsharp
1219 | module rec CarModule
1220 |
1221 | exception OutOfGasException of Car // Car not defined yet; would be an error
1222 |
1223 | type Car =
1224 | { make: string; model: string; hasGas: bool }
1225 | member self.Drive destination =
1226 | if not self.hasGas
1227 | then raise (OutOfGasException self)
1228 | else ...
1229 | ```
1230 | ```fsharp
1231 | type Person =
1232 | { Name: string; Address: Address }
1233 | and Address =
1234 | { Line1: string; Line2: string; Occupant: Person }
1235 | ```
1236 |
1237 | 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.
1238 |
1239 |
1240 |
1241 | # Compiler Directives
1242 |
1243 | ## time
1244 |
1245 | The `dotnet fsi` directive, `#time` switches on basic metrics covering real time, CPU time, and garbage collection information.
1246 |
1247 | ```fsharp
1248 | #time
1249 | System.Threading.Thread.Sleep (System.TimeSpan.FromSeconds 1)
1250 | #time
1251 | ```
1252 |
1253 | Output:
1254 |
1255 | --> Timing now on
1256 | Real: 00:00:01.001, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
1257 | val it: unit = ()
1258 | --> Timing now off
1259 |
1260 | ## load
1261 |
1262 | Load another F# source file into FSI.
1263 |
1264 | ```fsharp
1265 | #load "../lib/StringParsing.fs"
1266 | ```
1267 |
1268 | ## Referencing packages or assemblies in a script
1269 |
1270 | Reference a .NET assembly (`/` symbol is recommended for Mono compatibility).
1271 | Reference a .NET assembly:
1272 |
1273 | ```fsharp
1274 | #r "../lib/FSharp.Markdown.dll"
1275 | ```
1276 |
1277 | Reference a nuget package
1278 |
1279 | ```fsharp
1280 | #r "nuget:Serilog.Sinks.Console" // latest production release
1281 | #r "nuget:FSharp.Data, 6.3.0" // specific version
1282 | #r "nuget:Equinox, *-*" // latest version, including `-alpha`, `-rc` version etc
1283 | ```
1284 |
1285 | Include a directory in assembly search paths.
1286 |
1287 | ```fsharp
1288 | #I "../lib"
1289 | #r "FSharp.Markdown.dll"
1290 | ```
1291 |
1292 | ## Other important directives
1293 |
1294 | Other important directives are conditional execution in FSI (`INTERACTIVE`) and querying current directory (`__SOURCE_DIRECTORY__`).
1295 |
1296 | ```fsharp
1297 | #if INTERACTIVE
1298 | let path = __SOURCE_DIRECTORY__ + "../lib"
1299 | #else
1300 | let path = "../../../lib"
1301 | #endif
1302 | ```
1303 |
--------------------------------------------------------------------------------
/docs/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{{title}}}
9 |
12 |
13 |
14 |
15 |
F# Cheatsheet
16 |
17 |
18 | This cheatsheet aims to succinctly cover the most important aspects of F# 8.0.
19 |
20 |
21 |
22 | The Microsoft
23 | F# Documentation
24 | is complete and authoritative and has received a lot of love in recent years; it's well worth the time investment to read. Only after you've got the lowdown here of course ;)
25 |
26 |
27 |
28 | This guide is a community effort.
29 | If you have any comments, corrections, or suggested additions,
30 | please open an issue or send a pull request to
31 | https://github.com/fsprojects/fsharp-cheatsheet.
32 | Questions are best addressed via the
33 | F# slack
34 | or the
35 | F# discord.
36 |
37 |
38 |
41 |
42 | {{{content}}}
43 |
44 |
45 |
--------------------------------------------------------------------------------
/lib/StringParsing.fs:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------------
2 | // F# Markdown (StringParsing.fs)
3 | // (c) Tomas Petricek, 2012, Available under Apache 2.0 license.
4 | // --------------------------------------------------------------------------------------
5 |
6 | module FSharp.Patterns
7 |
8 | open System
9 | open FSharp.Collections
10 |
11 | // --------------------------------------------------------------------------------------
12 | // Active patterns that simplify parsing of strings and lists of strings (lines)
13 | // --------------------------------------------------------------------------------------
14 |
15 | module String =
16 | /// Matches when a string is a whitespace or null
17 | let (|WhiteSpace|_|) s =
18 | if String.IsNullOrWhiteSpace(s) then Some() else None
19 |
20 | /// Matches when a string does starts with non-whitespace
21 | let (|Unindented|_|) (s:string) =
22 | if not (String.IsNullOrWhiteSpace(s)) && s.TrimStart() = s then Some() else None
23 |
24 | /// Returns a string trimmed from both start and end
25 | let (|TrimBoth|) (text:string) = text.Trim()
26 | /// Returns a string trimmed from the end
27 | let (|TrimEnd|) (text:string) = text.TrimEnd()
28 | /// Returns a string trimmed from the start
29 | let (|TrimStart|) (text:string) = text.TrimStart()
30 |
31 | /// Retrusn a string trimmed from the end using characters given as a parameter
32 | let (|TrimEndUsing|) chars (text:string) = text.TrimEnd(Array.ofSeq chars)
33 |
34 | /// Returns a string trimmed from the start together with
35 | /// the number of skipped whitespace characters
36 | let (|TrimStartAndCount|) (text:string) =
37 | let trimmed = text.TrimStart()
38 | text.Length - trimmed.Length, trimmed
39 |
40 | /// Matches when a string starts with any of the specified sub-strings
41 | let (|StartsWithAny|_|) (starts:seq) (text:string) =
42 | if starts |> Seq.exists (text.StartsWith) then Some() else None
43 | /// Matches when a string starts with the specified sub-string
44 | let (|StartsWith|_|) (start:string) (text:string) =
45 | if text.StartsWith(start) then Some(text.Substring(start.Length)) else None
46 | /// Matches when a string starts with the specified sub-string
47 | /// The matched string is trimmed from all whitespace.
48 | let (|StartsWithTrim|_|) (start:string) (text:string) =
49 | if text.StartsWith(start) then Some(text.Substring(start.Length).Trim()) else None
50 |
51 | /// Matches when a string starts with the given value and ends
52 | /// with a given value (and returns the rest of it)
53 | let (|StartsAndEndsWith|_|) (starts, ends) (s:string) =
54 | if s.StartsWith(starts) && s.EndsWith(ends) &&
55 | s.Length >= starts.Length + ends.Length then
56 | Some(s.Substring(starts.Length, s.Length - starts.Length - ends.Length))
57 | else None
58 |
59 | /// Matches when a string starts with the given value and ends
60 | /// with a given value (and returns trimmed body)
61 | let (|StartsAndEndsWithTrim|_|) args = function
62 | | StartsAndEndsWith args (TrimBoth res) -> Some res
63 | | _ -> None
64 |
65 | /// Matches when a string starts with a non-zero number of complete
66 | /// repetitions of the specified parameter (and returns the number
67 | /// of repetitions, together with the rest of the string)
68 | ///
69 | /// let (StartsWithRepeated "/\" (2, " abc")) = "/\/\ abc"
70 | ///
71 | let (|StartsWithRepeated|_|) (repeated:string) (text:string) =
72 | let rec loop i =
73 | if i = text.Length then i
74 | elif text.[i] <> repeated.[i % repeated.Length] then i
75 | else loop (i + 1)
76 |
77 | let n = loop 0
78 | if n = 0 || n % repeated.Length <> 0 then None
79 | else Some(n/repeated.Length, text.Substring(n, text.Length - n))
80 |
81 | /// Matches when a string starts with a sub-string wrapped using the
82 | /// opening and closing sub-string specified in the parameter.
83 | /// For example "[aa]bc" is wrapped in [ and ] pair. Returns the wrapped
84 | /// text together with the rest.
85 | let (|StartsWithWrapped|_|) (starts:string, ends:string) (text:string) =
86 | if text.StartsWith(starts) then
87 | let id = text.IndexOf(ends, starts.Length)
88 | if id >= 0 then
89 | let wrapped = text.Substring(starts.Length, id - starts.Length)
90 | let rest = text.Substring(id + ends.Length, text.Length - id - ends.Length)
91 | Some(wrapped, rest)
92 | else None
93 | else None
94 |
95 | /// Matches when a string consists of some number of
96 | /// complete repetitions of a specified sub-string.
97 | let (|EqualsRepeated|_|) repeated = function
98 | | StartsWithRepeated repeated (n, "") -> Some()
99 | | _ -> None
100 |
101 | module List =
102 | /// Matches a list if it starts with a sub-list that is delimited
103 | /// using the specified delimiters. Returns a wrapped list and the rest.
104 | let inline (|DelimitedWith|_|) startl endl input =
105 | if List.startsWith startl input then
106 | match List.partitionUntilEquals endl (List.skip startl.Length input) with
107 | | Some(pre, post) -> Some(pre, List.skip endl.Length post)
108 | | None -> None
109 | else None
110 |
111 | /// Matches a list if it starts with a sub-list that is delimited
112 | /// using the specified delimiter. Returns a wrapped list and the rest.
113 | let inline (|Delimited|_|) str = (|DelimitedWith|_|) str str
114 |
115 | /// Matches a list if it starts with a bracketed list. Nested brackets
116 | /// are skipped (by counting opening and closing brackets) and can be
117 | /// escaped using the '\' symbol.
118 | let (|BracketDelimited|_|) startc endc input =
119 | let rec loop acc count = function
120 | | '\\'::x::xs when x = endc -> loop (x::acc) count xs
121 | | x::xs when x = endc && count = 0 -> Some(List.rev acc, xs)
122 | | x::xs when x = endc -> loop (x::acc) (count - 1) xs
123 | | x::xs when x = startc -> loop (x::acc) (count + 1) xs
124 | | x::xs -> loop (x::acc) count xs
125 | | [] -> None
126 | match input with
127 | | x::xs when x = startc -> loop [] 0 xs
128 | | _ -> None
129 |
130 | /// Retruns a list of characters as a string.
131 | let (|AsString|) chars = String(Array.ofList chars)
132 |
133 | module Lines =
134 | /// Removes blank lines from the start and the end of a list
135 | let (|TrimBlank|) lines =
136 | lines
137 | |> List.skipWhile String.IsNullOrWhiteSpace |> List.rev
138 | |> List.skipWhile String.IsNullOrWhiteSpace |> List.rev
139 |
140 | /// Matches when there are some lines at the beginning that are
141 | /// either empty (or whitespace) or start with the specified string.
142 | /// Returns all such lines from the beginning until a different line.
143 | let (|TakeStartingWithOrBlank|_|) start input =
144 | match List.partitionWhile (fun s ->
145 | String.IsNullOrWhiteSpace s || s.StartsWith(start)) input with
146 | | matching, rest when matching <> [] -> Some(matching, rest)
147 | | _ -> None
148 |
149 | /// Removes whitespace lines from the beginning of the list
150 | let (|TrimBlankStart|) = List.skipWhile (String.IsNullOrWhiteSpace)
151 |
152 |
153 | /// Parameterized pattern that assigns the specified value to the
154 | /// first component of a tuple. Usage:
155 | ///
156 | /// match str with
157 | /// | Let 1 (n, "one") | Let 2 (n, "two") -> n
158 | ///
159 | let (|Let|) a b = (a, b)
160 |
161 |
--------------------------------------------------------------------------------