├── .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 | --------------------------------------------------------------------------------