├── .gitattributes ├── .gitignore ├── .ionide └── symbolCache.db ├── .vscode └── extensions.json ├── Completed ├── FSharpWorkshopCompleted.sln ├── Module1 │ ├── Application │ │ ├── Application.fsproj │ │ ├── Functions.fs │ │ ├── Try.fsx │ │ └── Types.fs │ ├── Demo.fsx │ └── Tests │ │ ├── Tests.fs │ │ └── Tests.fsproj ├── Module2 │ ├── Application │ │ ├── Application.fsproj │ │ ├── Functions.fs │ │ ├── Try.fsx │ │ └── Types.fs │ ├── Demo.fsx │ └── Tests │ │ ├── Tests.fs │ │ └── Tests.fsproj ├── Module3 │ ├── Application │ │ ├── Application.fsproj │ │ ├── Functions.fs │ │ ├── Try.fsx │ │ └── Types.fs │ ├── Demo.fsx │ └── Tests │ │ ├── Tests.fs │ │ └── Tests.fsproj └── Module4 │ ├── Application │ ├── Application.fsproj │ ├── Data.json │ ├── Functions.fs │ ├── Program.fs │ ├── Services.fs │ ├── Try.fsx │ └── Types.fs │ ├── Demo.fsx │ └── Tests │ ├── Tests.fs │ └── Tests.fsproj ├── FSharpWorkshop.sln ├── FSharpWorkshop_Exercises.docx ├── FSharpWorkshop_Exercises.pdf ├── FSharpWorkshop_Slides.pdf ├── FSharpWorkshop_Slides.pptx ├── LICENSE ├── Module1 ├── Application │ ├── Application.fsproj │ ├── Functions.fs │ ├── Try.fsx │ └── Types.fs ├── Demo.fsx └── Tests │ ├── Tests.fs │ └── Tests.fsproj ├── Module2 ├── Application │ ├── Application.fsproj │ ├── Functions.fs │ ├── Try.fsx │ └── Types.fs ├── Demo.fsx └── Tests │ ├── Tests.fs │ └── Tests.fsproj ├── Module3 ├── Application │ ├── Application.fsproj │ ├── Functions.fs │ ├── Try.fsx │ └── Types.fs ├── Demo.fsx └── Tests │ ├── Tests.fs │ └── Tests.fsproj ├── Module4 ├── Application │ ├── Application.fsproj │ ├── Data.json │ ├── Functions.fs │ ├── Program.fs │ ├── Services.fs │ ├── Try.fsx │ ├── Types.fs │ └── paket.references ├── Demo.fsx └── Tests │ ├── Tests.fs │ └── Tests.fsproj └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | [Rr]eleases/ 14 | x64/ 15 | x86/ 16 | build/ 17 | bld/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | 21 | # Roslyn cache directories 22 | *.ide/ 23 | 24 | # MSTest test Results 25 | [Tt]est[Rr]esult*/ 26 | [Bb]uild[Ll]og.* 27 | 28 | #NUNIT 29 | *.VisualState.xml 30 | TestResult.xml 31 | 32 | # Build Results of an ATL Project 33 | [Dd]ebugPS/ 34 | [Rr]eleasePS/ 35 | dlldata.c 36 | 37 | *_i.c 38 | *_p.c 39 | *_i.h 40 | *.ilk 41 | *.meta 42 | *.obj 43 | *.pch 44 | *.pdb 45 | *.pgc 46 | *.pgd 47 | *.rsp 48 | *.sbr 49 | *.tlb 50 | *.tli 51 | *.tlh 52 | *.tmp 53 | *.tmp_proj 54 | *.log 55 | *.vspscc 56 | *.vssscc 57 | .builds 58 | *.pidb 59 | *.svclog 60 | *.scc 61 | 62 | # Chutzpah Test files 63 | _Chutzpah* 64 | 65 | # Visual C++ cache files 66 | ipch/ 67 | *.aps 68 | *.ncb 69 | *.opensdf 70 | *.sdf 71 | *.cachefile 72 | 73 | # Visual Studio profiler 74 | *.psess 75 | *.vsp 76 | *.vspx 77 | 78 | # TFS 2012 Local Workspace 79 | $tf/ 80 | 81 | # Guidance Automation Toolkit 82 | *.gpState 83 | 84 | # ReSharper is a .NET coding add-in 85 | _ReSharper*/ 86 | *.[Rr]e[Ss]harper 87 | *.DotSettings.user 88 | 89 | # JustCode is a .NET coding addin-in 90 | .JustCode 91 | 92 | # TeamCity is a build add-in 93 | _TeamCity* 94 | 95 | # DotCover is a Code Coverage Tool 96 | *.dotCover 97 | 98 | # NCrunch 99 | _NCrunch_* 100 | .*crunch*.local.xml 101 | 102 | # MightyMoose 103 | *.mm.* 104 | AutoTest.Net/ 105 | 106 | # Web workbench (sass) 107 | .sass-cache/ 108 | 109 | # Installshield output folder 110 | [Ee]xpress/ 111 | 112 | # DocProject is a documentation generator add-in 113 | DocProject/buildhelp/ 114 | DocProject/Help/*.HxT 115 | DocProject/Help/*.HxC 116 | DocProject/Help/*.hhc 117 | DocProject/Help/*.hhk 118 | DocProject/Help/*.hhp 119 | DocProject/Help/Html2 120 | DocProject/Help/html 121 | 122 | # Click-Once directory 123 | publish/ 124 | 125 | # Publish Web Output 126 | *.[Pp]ublish.xml 127 | *.azurePubxml 128 | # TODO: Comment the next line if you want to checkin your web deploy settings 129 | # but database connection strings (with potential passwords) will be unencrypted 130 | *.pubxml 131 | *.publishproj 132 | 133 | # NuGet Packages 134 | *.nupkg 135 | # The packages folder can be ignored because of Package Restore 136 | **/packages/* 137 | # except build/, which is used as an MSBuild target. 138 | !**/packages/build/ 139 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 140 | #!**/packages/repositories.config 141 | 142 | # Windows Azure Build Output 143 | csx/ 144 | *.build.csdef 145 | 146 | # Windows Store app package directory 147 | AppPackages/ 148 | 149 | # Others 150 | sql/ 151 | *.Cache 152 | ClientBin/ 153 | [Ss]tyle[Cc]op.* 154 | ~$* 155 | *~ 156 | *.dbmdl 157 | *.dbproj.schemaview 158 | *.pfx 159 | *.publishsettings 160 | node_modules/ 161 | 162 | # RIA/Silverlight projects 163 | Generated_Code/ 164 | 165 | # Backup & report files from converting an old project file 166 | # to a newer Visual Studio version. Backup files are not needed, 167 | # because we have git ;-) 168 | _UpgradeReport_Files/ 169 | Backup*/ 170 | UpgradeLog*.XML 171 | UpgradeLog*.htm 172 | 173 | # SQL Server files 174 | *.mdf 175 | *.ldf 176 | 177 | # Business Intelligence projects 178 | *.rdl.data 179 | *.bim.layout 180 | *.bim_*.settings 181 | 182 | # Microsoft Fakes 183 | FakesAssemblies/ 184 | 185 | # =================================================== 186 | # Exclude F# project specific directories and files 187 | # =================================================== 188 | 189 | # NuGet Packages Directory 190 | packages/ 191 | 192 | # Generated documentation folder 193 | docs/output/ 194 | 195 | # Temp folder used for publishing docs 196 | temp/ 197 | 198 | # Test results produced by build 199 | TestResults.xml 200 | 201 | # Nuget outputs 202 | nuget/*.nupkg 203 | release.cmd 204 | release.sh 205 | localpackages/ 206 | paket-files 207 | *.orig 208 | .paket/paket.exe 209 | docs/content/license.md 210 | docs/content/release-notes.md 211 | .fake 212 | docs/tools/FSharp.Formatting.svclog 213 | testsOutput/ 214 | appOutput/ 215 | .vs/ -------------------------------------------------------------------------------- /.ionide/symbolCache.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgef/fsharpworkshop/f06b9e0089bb2d13e953b30fdcd1a1a01adf1698/.ionide/symbolCache.db -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ionide.ionide-fsharp" 4 | ] 5 | } -------------------------------------------------------------------------------- /Completed/FSharpWorkshopCompleted.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Module1", "Module1", "{E022C6AD-65E1-4C44-A463-02C4A74B8EF1}" 7 | EndProject 8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Application", "Module1\Application\Application.fsproj", "{6A431D66-F545-441A-82CD-5B85ADD0F7C0}" 9 | EndProject 10 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Tests", "Module1\Tests\Tests.fsproj", "{DDA9C6FC-F823-4C19-8424-E2AA84BFFFF6}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Module2", "Module2", "{DD79A08C-010B-4EC8-895F-2AFEB2965E98}" 13 | EndProject 14 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Application", "Module2\Application\Application.fsproj", "{BE23CC93-43AC-4A50-926F-3786F5B4E70A}" 15 | EndProject 16 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Tests", "Module2\Tests\Tests.fsproj", "{FE0D694D-C926-47F1-B803-24D950F081AF}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Module3", "Module3", "{D7B10CEF-35E3-4555-9FD0-9D6D398A2E06}" 19 | EndProject 20 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Application", "Module3\Application\Application.fsproj", "{369DFD0D-5358-4440-8F27-49FD9FC288A7}" 21 | EndProject 22 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Tests", "Module3\Tests\Tests.fsproj", "{B6ED19F4-06B9-446A-A2D1-6064AA5AC641}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Module4", "Module4", "{90309E30-02DF-4E18-A43D-55438D7A521D}" 25 | EndProject 26 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Application", "Module4\Application\Application.fsproj", "{69D1888C-EAF2-4DF0-BCD1-0BB9A481E568}" 27 | EndProject 28 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Tests", "Module4\Tests\Tests.fsproj", "{2C2EB367-DAE0-4DB0-9CA6-048D332DEDFB}" 29 | EndProject 30 | Global 31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 32 | Debug|Any CPU = Debug|Any CPU 33 | Debug|x64 = Debug|x64 34 | Debug|x86 = Debug|x86 35 | Release|Any CPU = Release|Any CPU 36 | Release|x64 = Release|x64 37 | Release|x86 = Release|x86 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 43 | {6A431D66-F545-441A-82CD-5B85ADD0F7C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {6A431D66-F545-441A-82CD-5B85ADD0F7C0}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {6A431D66-F545-441A-82CD-5B85ADD0F7C0}.Debug|x64.ActiveCfg = Debug|Any CPU 46 | {6A431D66-F545-441A-82CD-5B85ADD0F7C0}.Debug|x64.Build.0 = Debug|Any CPU 47 | {6A431D66-F545-441A-82CD-5B85ADD0F7C0}.Debug|x86.ActiveCfg = Debug|Any CPU 48 | {6A431D66-F545-441A-82CD-5B85ADD0F7C0}.Debug|x86.Build.0 = Debug|Any CPU 49 | {6A431D66-F545-441A-82CD-5B85ADD0F7C0}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {6A431D66-F545-441A-82CD-5B85ADD0F7C0}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {6A431D66-F545-441A-82CD-5B85ADD0F7C0}.Release|x64.ActiveCfg = Release|Any CPU 52 | {6A431D66-F545-441A-82CD-5B85ADD0F7C0}.Release|x64.Build.0 = Release|Any CPU 53 | {6A431D66-F545-441A-82CD-5B85ADD0F7C0}.Release|x86.ActiveCfg = Release|Any CPU 54 | {6A431D66-F545-441A-82CD-5B85ADD0F7C0}.Release|x86.Build.0 = Release|Any CPU 55 | {DDA9C6FC-F823-4C19-8424-E2AA84BFFFF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {DDA9C6FC-F823-4C19-8424-E2AA84BFFFF6}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {DDA9C6FC-F823-4C19-8424-E2AA84BFFFF6}.Debug|x64.ActiveCfg = Debug|Any CPU 58 | {DDA9C6FC-F823-4C19-8424-E2AA84BFFFF6}.Debug|x64.Build.0 = Debug|Any CPU 59 | {DDA9C6FC-F823-4C19-8424-E2AA84BFFFF6}.Debug|x86.ActiveCfg = Debug|Any CPU 60 | {DDA9C6FC-F823-4C19-8424-E2AA84BFFFF6}.Debug|x86.Build.0 = Debug|Any CPU 61 | {DDA9C6FC-F823-4C19-8424-E2AA84BFFFF6}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {DDA9C6FC-F823-4C19-8424-E2AA84BFFFF6}.Release|Any CPU.Build.0 = Release|Any CPU 63 | {DDA9C6FC-F823-4C19-8424-E2AA84BFFFF6}.Release|x64.ActiveCfg = Release|Any CPU 64 | {DDA9C6FC-F823-4C19-8424-E2AA84BFFFF6}.Release|x64.Build.0 = Release|Any CPU 65 | {DDA9C6FC-F823-4C19-8424-E2AA84BFFFF6}.Release|x86.ActiveCfg = Release|Any CPU 66 | {DDA9C6FC-F823-4C19-8424-E2AA84BFFFF6}.Release|x86.Build.0 = Release|Any CPU 67 | {BE23CC93-43AC-4A50-926F-3786F5B4E70A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 68 | {BE23CC93-43AC-4A50-926F-3786F5B4E70A}.Debug|Any CPU.Build.0 = Debug|Any CPU 69 | {BE23CC93-43AC-4A50-926F-3786F5B4E70A}.Debug|x64.ActiveCfg = Debug|Any CPU 70 | {BE23CC93-43AC-4A50-926F-3786F5B4E70A}.Debug|x64.Build.0 = Debug|Any CPU 71 | {BE23CC93-43AC-4A50-926F-3786F5B4E70A}.Debug|x86.ActiveCfg = Debug|Any CPU 72 | {BE23CC93-43AC-4A50-926F-3786F5B4E70A}.Debug|x86.Build.0 = Debug|Any CPU 73 | {BE23CC93-43AC-4A50-926F-3786F5B4E70A}.Release|Any CPU.ActiveCfg = Release|Any CPU 74 | {BE23CC93-43AC-4A50-926F-3786F5B4E70A}.Release|Any CPU.Build.0 = Release|Any CPU 75 | {BE23CC93-43AC-4A50-926F-3786F5B4E70A}.Release|x64.ActiveCfg = Release|Any CPU 76 | {BE23CC93-43AC-4A50-926F-3786F5B4E70A}.Release|x64.Build.0 = Release|Any CPU 77 | {BE23CC93-43AC-4A50-926F-3786F5B4E70A}.Release|x86.ActiveCfg = Release|Any CPU 78 | {BE23CC93-43AC-4A50-926F-3786F5B4E70A}.Release|x86.Build.0 = Release|Any CPU 79 | {FE0D694D-C926-47F1-B803-24D950F081AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 80 | {FE0D694D-C926-47F1-B803-24D950F081AF}.Debug|Any CPU.Build.0 = Debug|Any CPU 81 | {FE0D694D-C926-47F1-B803-24D950F081AF}.Debug|x64.ActiveCfg = Debug|Any CPU 82 | {FE0D694D-C926-47F1-B803-24D950F081AF}.Debug|x64.Build.0 = Debug|Any CPU 83 | {FE0D694D-C926-47F1-B803-24D950F081AF}.Debug|x86.ActiveCfg = Debug|Any CPU 84 | {FE0D694D-C926-47F1-B803-24D950F081AF}.Debug|x86.Build.0 = Debug|Any CPU 85 | {FE0D694D-C926-47F1-B803-24D950F081AF}.Release|Any CPU.ActiveCfg = Release|Any CPU 86 | {FE0D694D-C926-47F1-B803-24D950F081AF}.Release|Any CPU.Build.0 = Release|Any CPU 87 | {FE0D694D-C926-47F1-B803-24D950F081AF}.Release|x64.ActiveCfg = Release|Any CPU 88 | {FE0D694D-C926-47F1-B803-24D950F081AF}.Release|x64.Build.0 = Release|Any CPU 89 | {FE0D694D-C926-47F1-B803-24D950F081AF}.Release|x86.ActiveCfg = Release|Any CPU 90 | {FE0D694D-C926-47F1-B803-24D950F081AF}.Release|x86.Build.0 = Release|Any CPU 91 | {369DFD0D-5358-4440-8F27-49FD9FC288A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 92 | {369DFD0D-5358-4440-8F27-49FD9FC288A7}.Debug|Any CPU.Build.0 = Debug|Any CPU 93 | {369DFD0D-5358-4440-8F27-49FD9FC288A7}.Debug|x64.ActiveCfg = Debug|Any CPU 94 | {369DFD0D-5358-4440-8F27-49FD9FC288A7}.Debug|x64.Build.0 = Debug|Any CPU 95 | {369DFD0D-5358-4440-8F27-49FD9FC288A7}.Debug|x86.ActiveCfg = Debug|Any CPU 96 | {369DFD0D-5358-4440-8F27-49FD9FC288A7}.Debug|x86.Build.0 = Debug|Any CPU 97 | {369DFD0D-5358-4440-8F27-49FD9FC288A7}.Release|Any CPU.ActiveCfg = Release|Any CPU 98 | {369DFD0D-5358-4440-8F27-49FD9FC288A7}.Release|Any CPU.Build.0 = Release|Any CPU 99 | {369DFD0D-5358-4440-8F27-49FD9FC288A7}.Release|x64.ActiveCfg = Release|Any CPU 100 | {369DFD0D-5358-4440-8F27-49FD9FC288A7}.Release|x64.Build.0 = Release|Any CPU 101 | {369DFD0D-5358-4440-8F27-49FD9FC288A7}.Release|x86.ActiveCfg = Release|Any CPU 102 | {369DFD0D-5358-4440-8F27-49FD9FC288A7}.Release|x86.Build.0 = Release|Any CPU 103 | {B6ED19F4-06B9-446A-A2D1-6064AA5AC641}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 104 | {B6ED19F4-06B9-446A-A2D1-6064AA5AC641}.Debug|Any CPU.Build.0 = Debug|Any CPU 105 | {B6ED19F4-06B9-446A-A2D1-6064AA5AC641}.Debug|x64.ActiveCfg = Debug|Any CPU 106 | {B6ED19F4-06B9-446A-A2D1-6064AA5AC641}.Debug|x64.Build.0 = Debug|Any CPU 107 | {B6ED19F4-06B9-446A-A2D1-6064AA5AC641}.Debug|x86.ActiveCfg = Debug|Any CPU 108 | {B6ED19F4-06B9-446A-A2D1-6064AA5AC641}.Debug|x86.Build.0 = Debug|Any CPU 109 | {B6ED19F4-06B9-446A-A2D1-6064AA5AC641}.Release|Any CPU.ActiveCfg = Release|Any CPU 110 | {B6ED19F4-06B9-446A-A2D1-6064AA5AC641}.Release|Any CPU.Build.0 = Release|Any CPU 111 | {B6ED19F4-06B9-446A-A2D1-6064AA5AC641}.Release|x64.ActiveCfg = Release|Any CPU 112 | {B6ED19F4-06B9-446A-A2D1-6064AA5AC641}.Release|x64.Build.0 = Release|Any CPU 113 | {B6ED19F4-06B9-446A-A2D1-6064AA5AC641}.Release|x86.ActiveCfg = Release|Any CPU 114 | {B6ED19F4-06B9-446A-A2D1-6064AA5AC641}.Release|x86.Build.0 = Release|Any CPU 115 | {69D1888C-EAF2-4DF0-BCD1-0BB9A481E568}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 116 | {69D1888C-EAF2-4DF0-BCD1-0BB9A481E568}.Debug|Any CPU.Build.0 = Debug|Any CPU 117 | {69D1888C-EAF2-4DF0-BCD1-0BB9A481E568}.Debug|x64.ActiveCfg = Debug|Any CPU 118 | {69D1888C-EAF2-4DF0-BCD1-0BB9A481E568}.Debug|x64.Build.0 = Debug|Any CPU 119 | {69D1888C-EAF2-4DF0-BCD1-0BB9A481E568}.Debug|x86.ActiveCfg = Debug|Any CPU 120 | {69D1888C-EAF2-4DF0-BCD1-0BB9A481E568}.Debug|x86.Build.0 = Debug|Any CPU 121 | {69D1888C-EAF2-4DF0-BCD1-0BB9A481E568}.Release|Any CPU.ActiveCfg = Release|Any CPU 122 | {69D1888C-EAF2-4DF0-BCD1-0BB9A481E568}.Release|Any CPU.Build.0 = Release|Any CPU 123 | {69D1888C-EAF2-4DF0-BCD1-0BB9A481E568}.Release|x64.ActiveCfg = Release|Any CPU 124 | {69D1888C-EAF2-4DF0-BCD1-0BB9A481E568}.Release|x64.Build.0 = Release|Any CPU 125 | {69D1888C-EAF2-4DF0-BCD1-0BB9A481E568}.Release|x86.ActiveCfg = Release|Any CPU 126 | {69D1888C-EAF2-4DF0-BCD1-0BB9A481E568}.Release|x86.Build.0 = Release|Any CPU 127 | {2C2EB367-DAE0-4DB0-9CA6-048D332DEDFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 128 | {2C2EB367-DAE0-4DB0-9CA6-048D332DEDFB}.Debug|Any CPU.Build.0 = Debug|Any CPU 129 | {2C2EB367-DAE0-4DB0-9CA6-048D332DEDFB}.Debug|x64.ActiveCfg = Debug|Any CPU 130 | {2C2EB367-DAE0-4DB0-9CA6-048D332DEDFB}.Debug|x64.Build.0 = Debug|Any CPU 131 | {2C2EB367-DAE0-4DB0-9CA6-048D332DEDFB}.Debug|x86.ActiveCfg = Debug|Any CPU 132 | {2C2EB367-DAE0-4DB0-9CA6-048D332DEDFB}.Debug|x86.Build.0 = Debug|Any CPU 133 | {2C2EB367-DAE0-4DB0-9CA6-048D332DEDFB}.Release|Any CPU.ActiveCfg = Release|Any CPU 134 | {2C2EB367-DAE0-4DB0-9CA6-048D332DEDFB}.Release|Any CPU.Build.0 = Release|Any CPU 135 | {2C2EB367-DAE0-4DB0-9CA6-048D332DEDFB}.Release|x64.ActiveCfg = Release|Any CPU 136 | {2C2EB367-DAE0-4DB0-9CA6-048D332DEDFB}.Release|x64.Build.0 = Release|Any CPU 137 | {2C2EB367-DAE0-4DB0-9CA6-048D332DEDFB}.Release|x86.ActiveCfg = Release|Any CPU 138 | {2C2EB367-DAE0-4DB0-9CA6-048D332DEDFB}.Release|x86.Build.0 = Release|Any CPU 139 | EndGlobalSection 140 | GlobalSection(NestedProjects) = preSolution 141 | {6A431D66-F545-441A-82CD-5B85ADD0F7C0} = {E022C6AD-65E1-4C44-A463-02C4A74B8EF1} 142 | {DDA9C6FC-F823-4C19-8424-E2AA84BFFFF6} = {E022C6AD-65E1-4C44-A463-02C4A74B8EF1} 143 | {BE23CC93-43AC-4A50-926F-3786F5B4E70A} = {DD79A08C-010B-4EC8-895F-2AFEB2965E98} 144 | {FE0D694D-C926-47F1-B803-24D950F081AF} = {DD79A08C-010B-4EC8-895F-2AFEB2965E98} 145 | {369DFD0D-5358-4440-8F27-49FD9FC288A7} = {D7B10CEF-35E3-4555-9FD0-9D6D398A2E06} 146 | {B6ED19F4-06B9-446A-A2D1-6064AA5AC641} = {D7B10CEF-35E3-4555-9FD0-9D6D398A2E06} 147 | {69D1888C-EAF2-4DF0-BCD1-0BB9A481E568} = {90309E30-02DF-4E18-A43D-55438D7A521D} 148 | {2C2EB367-DAE0-4DB0-9CA6-048D332DEDFB} = {90309E30-02DF-4E18-A43D-55438D7A521D} 149 | EndGlobalSection 150 | EndGlobal 151 | -------------------------------------------------------------------------------- /Completed/Module1/Application/Application.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Completed/Module1/Application/Functions.fs: -------------------------------------------------------------------------------- 1 | module Functions 2 | 3 | open Types 4 | 5 | let tryPromoteToVip purchases = 6 | let customer, amount = purchases 7 | if amount > 100M then { customer with IsVip = true } 8 | else customer 9 | 10 | let getPurchases customer = 11 | if customer.Id % 2 = 0 then (customer, 120M) 12 | else (customer, 80M) -------------------------------------------------------------------------------- /Completed/Module1/Application/Try.fsx: -------------------------------------------------------------------------------- 1 | #load "Types.fs" 2 | #load "Functions.fs" 3 | 4 | open Types 5 | open Functions 6 | 7 | let customer = { Id =1; IsVip = false; Credit = 10M } 8 | 9 | let purchases = (customer, 101M) 10 | let vipCustomer = tryPromoteToVip purchases 11 | 12 | let calculatedPurchases = getPurchases customer -------------------------------------------------------------------------------- /Completed/Module1/Application/Types.fs: -------------------------------------------------------------------------------- 1 | module Types 2 | 3 | type Customer = { 4 | Id: int 5 | IsVip: bool 6 | Credit: decimal 7 | } -------------------------------------------------------------------------------- /Completed/Module1/Demo.fsx: -------------------------------------------------------------------------------- 1 | // Demo 1 2 | 3 | let a = 1 4 | let b = 2 5 | let sum x y = x + y 6 | let res = sum a b 7 | 8 | let myTuple = (42, "hello") 9 | let number, message = myTuple 10 | 11 | type MyRecord = { Number: int; Message: string } 12 | let myRecord = { Number = 42; Message = "hello" } 13 | let newRecord = { myRecord with Message = "hi" } -------------------------------------------------------------------------------- /Completed/Module1/Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | module Tests 2 | 3 | open Xunit 4 | open Swensen.Unquote 5 | open Types 6 | open Functions 7 | 8 | [] 9 | let ``1-1 Create customer``() = 10 | let customer = { Id = 1; IsVip = false; Credit = 0M } 11 | test <@ customer.GetType () = typeof @> 12 | 13 | [] 14 | let ``1-2 Promote to vip``() = 15 | let customer = { Id = 1; IsVip = false; Credit = 0M } 16 | let promotedCustomer = tryPromoteToVip (customer, 100.1M) 17 | test <@ promotedCustomer.IsVip = true @> 18 | 19 | [] 20 | let ``1-3 Do not promote to vip``() = 21 | let customer = { Id = 1; IsVip = false; Credit = 0M } 22 | let promotedCustomer = tryPromoteToVip (customer, 99.9M) 23 | test <@ promotedCustomer.IsVip = false @> 24 | 25 | [] 26 | let ``1-4 Get purchases for odd customers``() = 27 | let customer = { Id = 1; IsVip = false; Credit = 0M } 28 | let _, purchases = getPurchases customer 29 | test <@ purchases = 80M @> 30 | 31 | [] 32 | let ``1-5 Get purchases for even customers``() = 33 | let customer = { Id = 2; IsVip = false; Credit = 0M } 34 | let _, purchases = getPurchases customer 35 | test <@ purchases = 120M @> -------------------------------------------------------------------------------- /Completed/Module1/Tests/Tests.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Completed/Module2/Application/Application.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Completed/Module2/Application/Functions.fs: -------------------------------------------------------------------------------- 1 | module Functions 2 | 3 | open Types 4 | 5 | let tryPromoteToVip purchases = 6 | let customer, amount = purchases 7 | if amount > 100M then { customer with IsVip = true } 8 | else customer 9 | 10 | let getPurchases customer = 11 | if customer.Id % 2 = 0 then (customer, 120M) 12 | else (customer, 80M) 13 | 14 | let increaseCredit condition customer = 15 | if condition customer then { customer with Credit = customer.Credit + 100M } 16 | else { customer with Credit = customer.Credit + 50M } 17 | 18 | let increaseCreditUsingVip = increaseCredit (fun c -> c.IsVip) 19 | 20 | let upgradeCustomer = getPurchases >> tryPromoteToVip >> increaseCreditUsingVip -------------------------------------------------------------------------------- /Completed/Module2/Application/Try.fsx: -------------------------------------------------------------------------------- 1 | #load "Types.fs" 2 | #load "Functions.fs" 3 | 4 | open Types 5 | open Functions 6 | 7 | let customer = { Id = 1; IsVip = false; Credit = 10M } 8 | 9 | let purchases = (customer, 101M) 10 | let vipCustomer = tryPromoteToVip purchases 11 | 12 | let calculatedPurchases = getPurchases customer 13 | 14 | let customerWithMoreCredit = increaseCredit (fun c -> c.IsVip) customer 15 | 16 | let upgradedCustomer = upgradeCustomer customer -------------------------------------------------------------------------------- /Completed/Module2/Application/Types.fs: -------------------------------------------------------------------------------- 1 | module Types 2 | 3 | type Customer = { 4 | Id: int 5 | IsVip: bool 6 | Credit: decimal 7 | } -------------------------------------------------------------------------------- /Completed/Module2/Demo.fsx: -------------------------------------------------------------------------------- 1 | // Demo 1 2 | 3 | let a = 1 4 | let b = 2 5 | let sum x y = x + y 6 | let res = sum a b 7 | 8 | let myTuple = (42, "hello") 9 | let number, message = myTuple 10 | 11 | type MyRecord = { Number: int; Message: string } 12 | let myRecord = { Number = 42; Message = "hello" } 13 | let newRecord = { myRecord with Message = "hi" } 14 | 15 | 16 | // Demo 2 17 | 18 | let compute (x: int) (y: int) (operation: int -> int -> int) = operation x y 19 | let res' = compute 1 2 sum 20 | 21 | let addOne = sum 1 22 | let addTwo = sum 2 23 | 24 | let res1 = addOne 1 25 | let res2 = addTwo res1 26 | 27 | let res2' = 28 | 1 29 | |> addOne 30 | |> addTwo 31 | 32 | let addThree = addOne >> addTwo 33 | let res2'' = addThree 1 -------------------------------------------------------------------------------- /Completed/Module2/Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | module Tests 2 | 3 | open Xunit 4 | open Swensen.Unquote 5 | open Types 6 | open Functions 7 | 8 | [] 9 | let ``2-1 Increase min credit using id``() = 10 | let customer = { Id = 1; IsVip = false; Credit = 0M } 11 | let upgradedCustomer = increaseCredit (fun c -> c.Id = 2) customer 12 | test <@ upgradedCustomer.Credit = 50M @> 13 | 14 | [] 15 | let ``2-2 Increase max credit using id``() = 16 | let customer = { Id = 2; IsVip = false; Credit = 0M } 17 | let upgradedCustomer = increaseCredit (fun c -> c.Id = 2) customer 18 | test <@ upgradedCustomer.Credit = 100M @> 19 | 20 | [] 21 | let ``2-3 Increase credit keeping existing one``() = 22 | let customer = { Id = 2; IsVip = false; Credit = 10M } 23 | let upgradedCustomer = increaseCredit (fun c -> c.Id = 2) customer 24 | test <@ upgradedCustomer.Credit = 110M @> 25 | 26 | [] 27 | let ``2-4 Increase max credit using vip``() = 28 | let customer = { Id = 2; IsVip = true; Credit = 0M } 29 | let upgradedCustomer = increaseCreditUsingVip customer 30 | test <@ upgradedCustomer.Credit = 100M @> 31 | 32 | [] 33 | let ``2-5 Upgrade customer with even id``() = 34 | let customer = { Id = 2; IsVip = false; Credit = 0M } 35 | let upgradedCustomer = upgradeCustomer customer 36 | test <@ upgradedCustomer.IsVip = true && upgradedCustomer.Credit = 100M @> 37 | 38 | [] 39 | let ``2-6 Upgrade customer with odd id``() = 40 | let customer = { Id = 1; IsVip = false; Credit = 0M } 41 | let upgradedCustomer = upgradeCustomer customer 42 | test <@ upgradedCustomer.IsVip = false && upgradedCustomer.Credit = 50M @> -------------------------------------------------------------------------------- /Completed/Module2/Tests/Tests.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Completed/Module3/Application/Application.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Completed/Module3/Application/Functions.fs: -------------------------------------------------------------------------------- 1 | module Functions 2 | 3 | open Types 4 | open System 5 | 6 | let tryPromoteToVip purchases = 7 | let customer, amount = purchases 8 | if amount > 100M then { customer with IsVip = true } 9 | else customer 10 | 11 | let getPurchases customer = 12 | if customer.Id % 2 = 0 then (customer, 120M) 13 | else (customer, 80M) 14 | 15 | let increaseCredit condition customer = 16 | if condition customer then { customer with Credit = customer.Credit + 100M } 17 | else { customer with Credit = customer.Credit + 50M } 18 | 19 | let increaseCreditUsingVip = increaseCredit (fun c -> c.IsVip) 20 | 21 | let upgradeCustomer = getPurchases >> tryPromoteToVip >> increaseCreditUsingVip 22 | 23 | let isAdult customer = 24 | match customer.PersonalDetails with 25 | | None -> false 26 | | Some d -> d.DateOfBirth.AddYears 18 <= DateTime.Now.Date 27 | 28 | let getAlert customer = 29 | match customer.Notifications with 30 | | ReceiveNotifications(receiveAlerts = true) -> 31 | sprintf "Alert for customer %i" customer.Id 32 | | _ -> "" -------------------------------------------------------------------------------- /Completed/Module3/Application/Try.fsx: -------------------------------------------------------------------------------- 1 | #load "Types.fs" 2 | #load "Functions.fs" 3 | 4 | open System 5 | open Types 6 | open Functions 7 | 8 | let customer = { 9 | Id = 1 10 | IsVip = false 11 | Credit = 0M 12 | PersonalDetails = Some { 13 | FirstName = "John" 14 | LastName = "Doe" 15 | DateOfBirth = DateTime(1970, 11, 23) 16 | } 17 | Notifications = ReceiveNotifications(receiveDeals = true, receiveAlerts = true) 18 | } 19 | 20 | let purchases = (customer, 101M) 21 | let vipCustomer = tryPromoteToVip purchases 22 | 23 | let calculatedPurchases = getPurchases customer 24 | 25 | let customerWithMoreCredit = customer |> increaseCredit (fun c -> c.IsVip) 26 | 27 | let upgradedCustomer = upgradeCustomer customer 28 | 29 | let isAdultCustomer = isAdult customer 30 | 31 | let alertCustomer = getAlert customer -------------------------------------------------------------------------------- /Completed/Module3/Application/Types.fs: -------------------------------------------------------------------------------- 1 | module Types 2 | 3 | open System 4 | 5 | type PersonalDetails = { 6 | FirstName: string 7 | LastName: string 8 | DateOfBirth: DateTime 9 | } 10 | 11 | [] type USD 12 | [] type EUR 13 | 14 | type Notifications = 15 | | NoNotifications 16 | | ReceiveNotifications of receiveDeals: bool * receiveAlerts: bool 17 | 18 | type Customer = { 19 | Id: int 20 | IsVip: bool 21 | Credit: decimal 22 | PersonalDetails: PersonalDetails option 23 | Notifications: Notifications 24 | } -------------------------------------------------------------------------------- /Completed/Module3/Demo.fsx: -------------------------------------------------------------------------------- 1 | // Demo 1 2 | 3 | let a = 1 4 | let b = 2 5 | let sum x y = x + y 6 | let res = sum a b 7 | 8 | let myTuple = (42, "hello") 9 | let number, message = myTuple 10 | 11 | type MyRecord = { Number: int; Message: string } 12 | let myRecord = { Number = 42; Message = "hello" } 13 | let newRecord = { myRecord with Message = "hi" } 14 | 15 | 16 | // Demo 2 17 | 18 | let compute (x: int) (y: int) (operation: int -> int -> int) = operation x y 19 | let res' = compute 1 2 sum 20 | 21 | let addOne = sum 1 22 | let addTwo = sum 2 23 | 24 | let res1 = addOne 1 25 | let res2 = addTwo res1 26 | 27 | let res2' = 28 | 1 29 | |> addOne 30 | |> addTwo 31 | 32 | let addThree = addOne >> addTwo 33 | let res2'' = addThree 1 34 | 35 | 36 | // Demo 3 37 | 38 | let divide x y = 39 | match y with 40 | | 0 -> None 41 | | _ -> Some(x/y) 42 | 43 | let result = divide 4 2 44 | let result' = divide 4 0 45 | 46 | type DivisionResult = 47 | | DivisionSuccess of result: int 48 | | DivisionError of message: string 49 | 50 | let divide' x y = 51 | match y with 52 | |0 -> DivisionError(message = "Divide by zero") 53 | |_ -> DivisionSuccess(result = x / y) 54 | 55 | let result'' = divide' 4 2 56 | let result''' = divide' 4 0 57 | 58 | [] type m; [] type km; [] type h 59 | let distanceInMts = 11580.0 60 | let distanceInKms = 87.34 61 | //let totalDistance = distanceInMts + distanceInKms // Error 62 | 63 | let convertToKms (mts: float) = 64 | let m = mts / 1.0 // remove unit of measure 65 | let k = m / 1000.0 // convert 66 | k * 1.0 // add new unit of measure 67 | 68 | let convertToKms' (mts: float) = mts / 1000.0 * 1.0 69 | 70 | let convertedToKms = convertToKms distanceInMts 71 | let totalDistance' = convertedToKms + distanceInKms 72 | let speed = totalDistance' / 2.4 -------------------------------------------------------------------------------- /Completed/Module3/Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | module Tests 2 | 3 | open System 4 | open Xunit 5 | open Swensen.Unquote 6 | open Types 7 | open Functions 8 | 9 | let customer = { 10 | Id = 1 11 | IsVip = false 12 | Credit = 0M 13 | PersonalDetails = Some { 14 | FirstName = "John" 15 | LastName = "Doe" 16 | DateOfBirth = DateTime(1970, 11, 23) } 17 | Notifications = ReceiveNotifications(receiveDeals = true, 18 | receiveAlerts = true) 19 | } 20 | 21 | [] 22 | let ``3-1 Create customer``() = 23 | test <@ customer.GetType() = typeof @> 24 | 25 | [] 26 | let ``3-2 Increase credit using USD``() = 27 | let upgradedCustomer = increaseCreditUsingVip customer 28 | test <@ upgradedCustomer.Credit = 50M @> 29 | 30 | [] 31 | let ``3-3 Adult customer``() = 32 | test <@ customer |> isAdult @> 33 | 34 | [] 35 | let ``3-4 Non-adult customer``() = 36 | let nonadult = { customer with PersonalDetails = Some { customer.PersonalDetails.Value with DateOfBirth = DateTime.Now.AddYears(-1) } } 37 | test <@ not (nonadult |> isAdult) @> 38 | 39 | [] 40 | let ``3-5 Customer without personal details``() = 41 | let nonadult = { customer with PersonalDetails = None } 42 | test <@ not (nonadult |> isAdult) @> 43 | 44 | [] 45 | let ``3-6 Get alert when nofications are enabled``() = 46 | let alert = customer |> getAlert 47 | test <@ alert = "Alert for customer 1" @> 48 | 49 | [] 50 | let ``3-7 Do not get alert when nofications are disabled``() = 51 | let alert = { customer with Notifications = NoNotifications } |> getAlert 52 | test <@ alert = "" @> -------------------------------------------------------------------------------- /Completed/Module3/Tests/Tests.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Completed/Module4/Application/Application.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Always 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Completed/Module4/Application/Data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "CustomerId": 1, 4 | "PurchasesByMonth": [ 60.0, 60.0, 60.0, 60.0, 60.0, 60.0, 60.0, 60.0, 60.0, 60.0, 60.0, 60.0 ] 5 | }, 6 | { 7 | "CustomerId": 2, 8 | "PurchasesByMonth": [ 150.60, 150.60, 150.60, 150.60, 150.60, 150.60, 150.60, 150.60, 150.60, 150.60, 150.60, 150.60 ] 9 | }, 10 | { 11 | "CustomerId": 3, 12 | "PurchasesByMonth": [ 80.30, 80.30, 80.30, 80.30, 80.30, 80.30, 80.30, 80.30, 80.30, 80.30, 80.30, 80.30 ] 13 | }, 14 | { 15 | "CustomerId": 4, 16 | "PurchasesByMonth": [ 130.20, 130.20, 130.20, 130.20, 130.20, 130.20, 130.20, 130.20, 130.20, 130.20, 130.20, 130.20 ] 17 | } 18 | ] -------------------------------------------------------------------------------- /Completed/Module4/Application/Functions.fs: -------------------------------------------------------------------------------- 1 | module Functions 2 | 3 | open Types 4 | open System 5 | open FSharp.Data 6 | 7 | let tryPromoteToVip purchases = 8 | let customer, amount = purchases 9 | if amount > 100M then { customer with IsVip = true } 10 | else customer 11 | 12 | let [] JsonExample = __SOURCE_DIRECTORY__ + "/Data.json" 13 | type Json = JsonProvider 14 | 15 | let getPurchases customer = 16 | let purchases = 17 | Json.Load "Data.json" 18 | |> Seq.filter (fun c -> c.CustomerId = customer.Id) 19 | |> Seq.collect (fun c -> c.PurchasesByMonth) 20 | |> Seq.average 21 | (customer, purchases) 22 | 23 | let increaseCredit condition customer = 24 | if condition customer then { customer with Credit = customer.Credit + 100M } 25 | else { customer with Credit = customer.Credit + 50M } 26 | 27 | let increaseCreditUsingVip = increaseCredit (fun c -> c.IsVip) 28 | 29 | let upgradeCustomer = getPurchases >> tryPromoteToVip >> increaseCreditUsingVip 30 | 31 | let isAdult customer = 32 | match customer.PersonalDetails with 33 | | None -> false 34 | | Some d -> d.DateOfBirth.AddYears 18 <= DateTime.Now.Date 35 | 36 | let getAlert customer = 37 | match customer.Notifications with 38 | | ReceiveNotifications(receiveAlerts = true) -> 39 | sprintf "Alert for customer %i" customer.Id 40 | | _ -> "" 41 | 42 | let getCustomer id = 43 | let customers = [ 44 | { Id = 1; IsVip = false; Credit = 0m; PersonalDetails = Some { FirstName = "John"; LastName = "Doe"; DateOfBirth = DateTime(1980, 1, 1) }; Notifications = NoNotifications } 45 | { Id = 2; IsVip = false; Credit = 10m; PersonalDetails = None; Notifications = ReceiveNotifications(true, true) } 46 | { Id = 3; IsVip = false; Credit = 30m; PersonalDetails = Some { FirstName = "Jane"; LastName = "Jones"; DateOfBirth = DateTime(2010, 2, 2) }; Notifications = ReceiveNotifications(true, false) } 47 | { Id = 4; IsVip = true; Credit = 50m; PersonalDetails = Some { FirstName = "Joe"; LastName = "Smith"; DateOfBirth = DateTime(1986, 3, 3) }; Notifications = ReceiveNotifications(false, true) } 48 | ] 49 | customers 50 | |> List.find (fun c -> c.Id = id) -------------------------------------------------------------------------------- /Completed/Module4/Application/Program.fs: -------------------------------------------------------------------------------- 1 | module Program 2 | 3 | open System 4 | open Services 5 | open Functions 6 | 7 | [] 8 | let rec main args = 9 | let service = CustomerService() 10 | printf "Id to upgrade [1-4]: " 11 | let valid, id = Console.ReadLine () |> Int32.TryParse 12 | printfn "" 13 | if not valid then 14 | printfn "Invalid customer Id" 15 | else 16 | printfn "Customer to upgrade:" 17 | let customerBefore = getCustomer id 18 | customerBefore |> service.GetCustomerInfo |> printfn "%s" 19 | printfn "" 20 | printfn "Upgrading customer..." 21 | let customerAfter = service.UpgradeCustomer id 22 | printfn "" 23 | printfn "Customer upgraded:" 24 | customerAfter |> service.GetCustomerInfo |> printfn "%s" 25 | printfn "" 26 | printfn "Press any key to try again or 'q' to quit" 27 | let input = Console.ReadKey () 28 | printfn "" 29 | if input.Key = ConsoleKey.Q then 0 else main args -------------------------------------------------------------------------------- /Completed/Module4/Application/Services.fs: -------------------------------------------------------------------------------- 1 | namespace Services 2 | 3 | namespace Services 4 | 5 | type CustomerService() = 6 | member this.UpgradeCustomer id = 7 | id 8 | |> Functions.getCustomer 9 | |> Functions.upgradeCustomer 10 | 11 | member this.GetCustomerInfo customer = 12 | let isAdult = Functions.isAdult customer 13 | let alert = Functions.getAlert customer 14 | sprintf "Id: %i, IsVip: %b, Credit: %.2f, IsAdult: %b, Alert: %s" 15 | customer.Id customer.IsVip customer.Credit isAdult alert -------------------------------------------------------------------------------- /Completed/Module4/Application/Try.fsx: -------------------------------------------------------------------------------- 1 | //#r @"/Users/[USERNAME]/.nuget/packages/fsharp.data/3.0.0-beta4/lib/netstandard2.0/FSharp.Data.dll" // Mac / Linux 2 | //#r @"%userprofile%\.nuget\packages\.nuget\packages\fsharp.data\3.0.0-beta4\lib\netstandard2.0\FSharp.Data.dll" // Windows 3 | 4 | #load "Types.fs" 5 | #load "Functions.fs" 6 | 7 | open System 8 | open Types 9 | open Functions 10 | 11 | let customer = { 12 | Id = 1 13 | IsVip = false 14 | Credit = 0M 15 | PersonalDetails = Some { 16 | FirstName = "John" 17 | LastName = "Doe" 18 | DateOfBirth = DateTime(1970, 11, 23) 19 | } 20 | Notifications = ReceiveNotifications(receiveDeals = true, receiveAlerts = true) 21 | } 22 | 23 | let purchases = (customer, 101M) 24 | let vipCustomer = tryPromoteToVip purchases 25 | 26 | let calculatedPurchases = getPurchases customer 27 | 28 | let customerWithMoreCredit = customer |> increaseCredit (fun c -> c.IsVip) 29 | 30 | let upgradedCustomer = upgradeCustomer customer 31 | 32 | let isAdultCustomer = isAdult customer 33 | 34 | let alertCustomer = getAlert customer -------------------------------------------------------------------------------- /Completed/Module4/Application/Types.fs: -------------------------------------------------------------------------------- 1 | module Types 2 | 3 | open System 4 | 5 | type PersonalDetails = { 6 | FirstName: string 7 | LastName: string 8 | DateOfBirth: DateTime 9 | } 10 | 11 | [] type EUR 12 | [] type USD 13 | 14 | type Notifications = 15 | | NoNotifications 16 | | ReceiveNotifications of receiveDeals: bool * receiveAlerts: bool 17 | 18 | type Customer = { 19 | Id: int 20 | IsVip: bool 21 | Credit: decimal 22 | PersonalDetails: PersonalDetails option 23 | Notifications: Notifications 24 | } -------------------------------------------------------------------------------- /Completed/Module4/Demo.fsx: -------------------------------------------------------------------------------- 1 | // Demo 1 2 | 3 | let a = 1 4 | let b = 2 5 | let sum x y = x + y 6 | let res = sum a b 7 | 8 | let myTuple = (42, "hello") 9 | let number, message = myTuple 10 | 11 | type MyRecord = { Number: int; Message: string } 12 | let myRecord = { Number = 42; Message = "hello" } 13 | let newRecord = { myRecord with Message = "hi" } 14 | 15 | 16 | // Demo 2 17 | 18 | let compute (x: int) (y: int) (operation: int -> int -> int) = operation x y 19 | let res' = compute 1 2 sum 20 | 21 | let addOne = sum 1 22 | let addTwo = sum 2 23 | 24 | let res1 = addOne 1 25 | let res2 = addTwo res1 26 | 27 | let res2' = 28 | 1 29 | |> addOne 30 | |> addTwo 31 | 32 | let addThree = addOne >> addTwo 33 | let res2'' = addThree 1 34 | 35 | 36 | // Demo 3 37 | 38 | let divide x y = 39 | match y with 40 | | 0 -> None 41 | | _ -> Some(x/y) 42 | 43 | let result = divide 4 2 44 | let result' = divide 4 0 45 | 46 | type DivisionResult = 47 | | DivisionSuccess of result: int 48 | | DivisionError of message: string 49 | 50 | let divide' x y = 51 | match y with 52 | |0 -> DivisionError(message = "Divide by zero") 53 | |_ -> DivisionSuccess(result = x / y) 54 | 55 | let result'' = divide' 4 2 56 | let result''' = divide' 4 0 57 | 58 | [] type m; [] type km; [] type h 59 | let distanceInMts = 11580.0 60 | let distanceInKms = 87.34 61 | //let totalDistance = distanceInMts + distanceInKms // Error 62 | 63 | let convertToKms (mts: float) = 64 | let m = mts / 1.0 // remove unit of measure 65 | let k = m / 1000.0 // convert 66 | k * 1.0 // add new unit of measure 67 | 68 | let convertToKms' (mts: float) = mts / 1000.0 * 1.0 69 | 70 | let convertedToKms = convertToKms distanceInMts 71 | let totalDistance' = convertedToKms + distanceInKms 72 | let speed = totalDistance' / 2.4 73 | 74 | 75 | // Demo 4 76 | 77 | let numbers = [1..100] 78 | let numbersWithZero = 0 :: numbers 79 | let evenNumbers = numbersWithZero |> List.filter (fun x -> x % 2 = 0) 80 | 81 | type MyClass(myField: int) = 82 | member this.MyProperty = myField 83 | member this.MyMethod methodParam = myField + methodParam 84 | 85 | let myInstance = MyClass(1) 86 | myInstance.MyProperty 87 | myInstance.MyMethod 2 88 | 89 | type IMyInterface = 90 | abstract member MyMethod: int -> int 91 | 92 | let myInstance' = 93 | { new IMyInterface with 94 | member this.MyMethod methodParam = 95 | methodParam + 1 } 96 | 97 | myInstance'.MyMethod 2 98 | 99 | //#r @"/Users/[USERNAME]/.nuget/packages/fsharp.data/3.0.0-beta4/lib/netstandard2.0/FSharp.Data.dll" // Mac / Linux 100 | //#r @"%userprofile%\.nuget\packages\.nuget\packages\fsharp.data\3.0.0-beta4\lib\netstandard2.0\FSharp.Data.dll" // Windows 101 | 102 | open FSharp.Data 103 | 104 | type Customer = JsonProvider<"Application/Data.json"> 105 | let customers = Customer.Load "Application/Data.json" 106 | 107 | customers 108 | |> Seq.iter (fun r -> printfn "%i: %A" r.CustomerId r.PurchasesByMonth) -------------------------------------------------------------------------------- /Completed/Module4/Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | module Tests 2 | 3 | open System 4 | open Xunit 5 | open Swensen.Unquote 6 | open Types 7 | open Functions 8 | open Services 9 | 10 | let customer = { 11 | Id = 1 12 | IsVip = false 13 | Credit = 0M 14 | PersonalDetails = Some { 15 | FirstName = "John" 16 | LastName = "Doe" 17 | DateOfBirth = DateTime(1970, 11, 23) } 18 | Notifications = ReceiveNotifications(receiveDeals = true, 19 | receiveAlerts = true) } 20 | 21 | 22 | [] 23 | let ``4-1 Get purchases average``() = 24 | let purchases = getPurchases customer 25 | test <@ purchases = (customer, 60M) @> 26 | 27 | [] 28 | let ``4-2 Upgrade customer``() = 29 | let service = CustomerService() 30 | let upgradedCustomer = service.UpgradeCustomer 2 31 | test <@ upgradedCustomer.IsVip @> 32 | test <@ upgradedCustomer.Credit = 110M @> 33 | 34 | [] 35 | let ``4-3 Get customer info``() = 36 | let service = CustomerService() 37 | let info = service.GetCustomerInfo customer 38 | let expectedInfo = "Id: 1, IsVip: false, Credit: 0.00, IsAdult: true, Alert: Alert for customer 1" 39 | test <@ info = expectedInfo @> -------------------------------------------------------------------------------- /Completed/Module4/Tests/Tests.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /FSharpWorkshop.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Module1", "Module1", "{6AEBDAFD-25F9-4787-A3C8-5E7631ED71A8}" 7 | ProjectSection(SolutionItems) = preProject 8 | Module1\Demo.fsx = Module1\Demo.fsx 9 | EndProjectSection 10 | EndProject 11 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Application", "Module1\Application\Application.fsproj", "{EBC8203C-8F54-4429-AD28-C0EDD45D9A52}" 12 | EndProject 13 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Tests", "Module1\Tests\Tests.fsproj", "{8EAF483D-535F-4CD6-AC98-4D828543B085}" 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Module2", "Module2", "{3EEDFB7B-016C-480D-A363-DF291DE7D5D2}" 16 | ProjectSection(SolutionItems) = preProject 17 | Module2\Demo.fsx = Module2\Demo.fsx 18 | EndProjectSection 19 | EndProject 20 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Application", "Module2\Application\Application.fsproj", "{7C108125-6659-41BC-9437-EA64164F0CE4}" 21 | EndProject 22 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Tests", "Module2\Tests\Tests.fsproj", "{60535DF9-ABD1-43D9-8411-9E8C3D25ACDF}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Module3", "Module3", "{C2417AC3-A6B8-46BC-83CF-6DFD9517F2DC}" 25 | ProjectSection(SolutionItems) = preProject 26 | Module3\Demo.fsx = Module3\Demo.fsx 27 | EndProjectSection 28 | EndProject 29 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Application", "Module3\Application\Application.fsproj", "{3F557C45-A5C6-487E-9612-96101172874C}" 30 | EndProject 31 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Tests", "Module3\Tests\Tests.fsproj", "{C26CA9FD-FB68-45BB-872C-57D78CA7CE4D}" 32 | EndProject 33 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Module4", "Module4", "{769CCC50-5EB4-40F7-86F2-F6228D0974D0}" 34 | ProjectSection(SolutionItems) = preProject 35 | Module4\Demo.fsx = Module4\Demo.fsx 36 | EndProjectSection 37 | EndProject 38 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Application", "Module4\Application\Application.fsproj", "{B3CABFE5-36F6-4657-9E31-16DD822B0220}" 39 | EndProject 40 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Tests", "Module4\Tests\Tests.fsproj", "{3CEDC805-64E4-4E9C-A98A-6452F7F0F415}" 41 | EndProject 42 | Global 43 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 44 | Debug|Any CPU = Debug|Any CPU 45 | Debug|x64 = Debug|x64 46 | Debug|x86 = Debug|x86 47 | Release|Any CPU = Release|Any CPU 48 | Release|x64 = Release|x64 49 | Release|x86 = Release|x86 50 | EndGlobalSection 51 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 52 | {EBC8203C-8F54-4429-AD28-C0EDD45D9A52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {EBC8203C-8F54-4429-AD28-C0EDD45D9A52}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {EBC8203C-8F54-4429-AD28-C0EDD45D9A52}.Debug|x64.ActiveCfg = Debug|Any CPU 55 | {EBC8203C-8F54-4429-AD28-C0EDD45D9A52}.Debug|x64.Build.0 = Debug|Any CPU 56 | {EBC8203C-8F54-4429-AD28-C0EDD45D9A52}.Debug|x86.ActiveCfg = Debug|Any CPU 57 | {EBC8203C-8F54-4429-AD28-C0EDD45D9A52}.Debug|x86.Build.0 = Debug|Any CPU 58 | {EBC8203C-8F54-4429-AD28-C0EDD45D9A52}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {EBC8203C-8F54-4429-AD28-C0EDD45D9A52}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {EBC8203C-8F54-4429-AD28-C0EDD45D9A52}.Release|x64.ActiveCfg = Release|Any CPU 61 | {EBC8203C-8F54-4429-AD28-C0EDD45D9A52}.Release|x64.Build.0 = Release|Any CPU 62 | {EBC8203C-8F54-4429-AD28-C0EDD45D9A52}.Release|x86.ActiveCfg = Release|Any CPU 63 | {EBC8203C-8F54-4429-AD28-C0EDD45D9A52}.Release|x86.Build.0 = Release|Any CPU 64 | {8EAF483D-535F-4CD6-AC98-4D828543B085}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 65 | {8EAF483D-535F-4CD6-AC98-4D828543B085}.Debug|Any CPU.Build.0 = Debug|Any CPU 66 | {8EAF483D-535F-4CD6-AC98-4D828543B085}.Debug|x64.ActiveCfg = Debug|Any CPU 67 | {8EAF483D-535F-4CD6-AC98-4D828543B085}.Debug|x64.Build.0 = Debug|Any CPU 68 | {8EAF483D-535F-4CD6-AC98-4D828543B085}.Debug|x86.ActiveCfg = Debug|Any CPU 69 | {8EAF483D-535F-4CD6-AC98-4D828543B085}.Debug|x86.Build.0 = Debug|Any CPU 70 | {8EAF483D-535F-4CD6-AC98-4D828543B085}.Release|Any CPU.ActiveCfg = Release|Any CPU 71 | {8EAF483D-535F-4CD6-AC98-4D828543B085}.Release|Any CPU.Build.0 = Release|Any CPU 72 | {8EAF483D-535F-4CD6-AC98-4D828543B085}.Release|x64.ActiveCfg = Release|Any CPU 73 | {8EAF483D-535F-4CD6-AC98-4D828543B085}.Release|x64.Build.0 = Release|Any CPU 74 | {8EAF483D-535F-4CD6-AC98-4D828543B085}.Release|x86.ActiveCfg = Release|Any CPU 75 | {8EAF483D-535F-4CD6-AC98-4D828543B085}.Release|x86.Build.0 = Release|Any CPU 76 | {7C108125-6659-41BC-9437-EA64164F0CE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 77 | {7C108125-6659-41BC-9437-EA64164F0CE4}.Debug|Any CPU.Build.0 = Debug|Any CPU 78 | {7C108125-6659-41BC-9437-EA64164F0CE4}.Debug|x64.ActiveCfg = Debug|Any CPU 79 | {7C108125-6659-41BC-9437-EA64164F0CE4}.Debug|x64.Build.0 = Debug|Any CPU 80 | {7C108125-6659-41BC-9437-EA64164F0CE4}.Debug|x86.ActiveCfg = Debug|Any CPU 81 | {7C108125-6659-41BC-9437-EA64164F0CE4}.Debug|x86.Build.0 = Debug|Any CPU 82 | {7C108125-6659-41BC-9437-EA64164F0CE4}.Release|Any CPU.ActiveCfg = Release|Any CPU 83 | {7C108125-6659-41BC-9437-EA64164F0CE4}.Release|Any CPU.Build.0 = Release|Any CPU 84 | {7C108125-6659-41BC-9437-EA64164F0CE4}.Release|x64.ActiveCfg = Release|Any CPU 85 | {7C108125-6659-41BC-9437-EA64164F0CE4}.Release|x64.Build.0 = Release|Any CPU 86 | {7C108125-6659-41BC-9437-EA64164F0CE4}.Release|x86.ActiveCfg = Release|Any CPU 87 | {7C108125-6659-41BC-9437-EA64164F0CE4}.Release|x86.Build.0 = Release|Any CPU 88 | {60535DF9-ABD1-43D9-8411-9E8C3D25ACDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 89 | {60535DF9-ABD1-43D9-8411-9E8C3D25ACDF}.Debug|Any CPU.Build.0 = Debug|Any CPU 90 | {60535DF9-ABD1-43D9-8411-9E8C3D25ACDF}.Debug|x64.ActiveCfg = Debug|Any CPU 91 | {60535DF9-ABD1-43D9-8411-9E8C3D25ACDF}.Debug|x64.Build.0 = Debug|Any CPU 92 | {60535DF9-ABD1-43D9-8411-9E8C3D25ACDF}.Debug|x86.ActiveCfg = Debug|Any CPU 93 | {60535DF9-ABD1-43D9-8411-9E8C3D25ACDF}.Debug|x86.Build.0 = Debug|Any CPU 94 | {60535DF9-ABD1-43D9-8411-9E8C3D25ACDF}.Release|Any CPU.ActiveCfg = Release|Any CPU 95 | {60535DF9-ABD1-43D9-8411-9E8C3D25ACDF}.Release|Any CPU.Build.0 = Release|Any CPU 96 | {60535DF9-ABD1-43D9-8411-9E8C3D25ACDF}.Release|x64.ActiveCfg = Release|Any CPU 97 | {60535DF9-ABD1-43D9-8411-9E8C3D25ACDF}.Release|x64.Build.0 = Release|Any CPU 98 | {60535DF9-ABD1-43D9-8411-9E8C3D25ACDF}.Release|x86.ActiveCfg = Release|Any CPU 99 | {60535DF9-ABD1-43D9-8411-9E8C3D25ACDF}.Release|x86.Build.0 = Release|Any CPU 100 | {3F557C45-A5C6-487E-9612-96101172874C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 101 | {3F557C45-A5C6-487E-9612-96101172874C}.Debug|Any CPU.Build.0 = Debug|Any CPU 102 | {3F557C45-A5C6-487E-9612-96101172874C}.Debug|x64.ActiveCfg = Debug|Any CPU 103 | {3F557C45-A5C6-487E-9612-96101172874C}.Debug|x64.Build.0 = Debug|Any CPU 104 | {3F557C45-A5C6-487E-9612-96101172874C}.Debug|x86.ActiveCfg = Debug|Any CPU 105 | {3F557C45-A5C6-487E-9612-96101172874C}.Debug|x86.Build.0 = Debug|Any CPU 106 | {3F557C45-A5C6-487E-9612-96101172874C}.Release|Any CPU.ActiveCfg = Release|Any CPU 107 | {3F557C45-A5C6-487E-9612-96101172874C}.Release|Any CPU.Build.0 = Release|Any CPU 108 | {3F557C45-A5C6-487E-9612-96101172874C}.Release|x64.ActiveCfg = Release|Any CPU 109 | {3F557C45-A5C6-487E-9612-96101172874C}.Release|x64.Build.0 = Release|Any CPU 110 | {3F557C45-A5C6-487E-9612-96101172874C}.Release|x86.ActiveCfg = Release|Any CPU 111 | {3F557C45-A5C6-487E-9612-96101172874C}.Release|x86.Build.0 = Release|Any CPU 112 | {C26CA9FD-FB68-45BB-872C-57D78CA7CE4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 113 | {C26CA9FD-FB68-45BB-872C-57D78CA7CE4D}.Debug|Any CPU.Build.0 = Debug|Any CPU 114 | {C26CA9FD-FB68-45BB-872C-57D78CA7CE4D}.Debug|x64.ActiveCfg = Debug|Any CPU 115 | {C26CA9FD-FB68-45BB-872C-57D78CA7CE4D}.Debug|x64.Build.0 = Debug|Any CPU 116 | {C26CA9FD-FB68-45BB-872C-57D78CA7CE4D}.Debug|x86.ActiveCfg = Debug|Any CPU 117 | {C26CA9FD-FB68-45BB-872C-57D78CA7CE4D}.Debug|x86.Build.0 = Debug|Any CPU 118 | {C26CA9FD-FB68-45BB-872C-57D78CA7CE4D}.Release|Any CPU.ActiveCfg = Release|Any CPU 119 | {C26CA9FD-FB68-45BB-872C-57D78CA7CE4D}.Release|Any CPU.Build.0 = Release|Any CPU 120 | {C26CA9FD-FB68-45BB-872C-57D78CA7CE4D}.Release|x64.ActiveCfg = Release|Any CPU 121 | {C26CA9FD-FB68-45BB-872C-57D78CA7CE4D}.Release|x64.Build.0 = Release|Any CPU 122 | {C26CA9FD-FB68-45BB-872C-57D78CA7CE4D}.Release|x86.ActiveCfg = Release|Any CPU 123 | {C26CA9FD-FB68-45BB-872C-57D78CA7CE4D}.Release|x86.Build.0 = Release|Any CPU 124 | {B3CABFE5-36F6-4657-9E31-16DD822B0220}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 125 | {B3CABFE5-36F6-4657-9E31-16DD822B0220}.Debug|Any CPU.Build.0 = Debug|Any CPU 126 | {B3CABFE5-36F6-4657-9E31-16DD822B0220}.Debug|x64.ActiveCfg = Debug|Any CPU 127 | {B3CABFE5-36F6-4657-9E31-16DD822B0220}.Debug|x64.Build.0 = Debug|Any CPU 128 | {B3CABFE5-36F6-4657-9E31-16DD822B0220}.Debug|x86.ActiveCfg = Debug|Any CPU 129 | {B3CABFE5-36F6-4657-9E31-16DD822B0220}.Debug|x86.Build.0 = Debug|Any CPU 130 | {B3CABFE5-36F6-4657-9E31-16DD822B0220}.Release|Any CPU.ActiveCfg = Release|Any CPU 131 | {B3CABFE5-36F6-4657-9E31-16DD822B0220}.Release|Any CPU.Build.0 = Release|Any CPU 132 | {B3CABFE5-36F6-4657-9E31-16DD822B0220}.Release|x64.ActiveCfg = Release|Any CPU 133 | {B3CABFE5-36F6-4657-9E31-16DD822B0220}.Release|x64.Build.0 = Release|Any CPU 134 | {B3CABFE5-36F6-4657-9E31-16DD822B0220}.Release|x86.ActiveCfg = Release|Any CPU 135 | {B3CABFE5-36F6-4657-9E31-16DD822B0220}.Release|x86.Build.0 = Release|Any CPU 136 | {3CEDC805-64E4-4E9C-A98A-6452F7F0F415}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 137 | {3CEDC805-64E4-4E9C-A98A-6452F7F0F415}.Debug|Any CPU.Build.0 = Debug|Any CPU 138 | {3CEDC805-64E4-4E9C-A98A-6452F7F0F415}.Debug|x64.ActiveCfg = Debug|Any CPU 139 | {3CEDC805-64E4-4E9C-A98A-6452F7F0F415}.Debug|x64.Build.0 = Debug|Any CPU 140 | {3CEDC805-64E4-4E9C-A98A-6452F7F0F415}.Debug|x86.ActiveCfg = Debug|Any CPU 141 | {3CEDC805-64E4-4E9C-A98A-6452F7F0F415}.Debug|x86.Build.0 = Debug|Any CPU 142 | {3CEDC805-64E4-4E9C-A98A-6452F7F0F415}.Release|Any CPU.ActiveCfg = Release|Any CPU 143 | {3CEDC805-64E4-4E9C-A98A-6452F7F0F415}.Release|Any CPU.Build.0 = Release|Any CPU 144 | {3CEDC805-64E4-4E9C-A98A-6452F7F0F415}.Release|x64.ActiveCfg = Release|Any CPU 145 | {3CEDC805-64E4-4E9C-A98A-6452F7F0F415}.Release|x64.Build.0 = Release|Any CPU 146 | {3CEDC805-64E4-4E9C-A98A-6452F7F0F415}.Release|x86.ActiveCfg = Release|Any CPU 147 | {3CEDC805-64E4-4E9C-A98A-6452F7F0F415}.Release|x86.Build.0 = Release|Any CPU 148 | EndGlobalSection 149 | GlobalSection(SolutionProperties) = preSolution 150 | HideSolutionNode = FALSE 151 | EndGlobalSection 152 | GlobalSection(NestedProjects) = preSolution 153 | {EBC8203C-8F54-4429-AD28-C0EDD45D9A52} = {6AEBDAFD-25F9-4787-A3C8-5E7631ED71A8} 154 | {8EAF483D-535F-4CD6-AC98-4D828543B085} = {6AEBDAFD-25F9-4787-A3C8-5E7631ED71A8} 155 | {7C108125-6659-41BC-9437-EA64164F0CE4} = {3EEDFB7B-016C-480D-A363-DF291DE7D5D2} 156 | {60535DF9-ABD1-43D9-8411-9E8C3D25ACDF} = {3EEDFB7B-016C-480D-A363-DF291DE7D5D2} 157 | {3F557C45-A5C6-487E-9612-96101172874C} = {C2417AC3-A6B8-46BC-83CF-6DFD9517F2DC} 158 | {C26CA9FD-FB68-45BB-872C-57D78CA7CE4D} = {C2417AC3-A6B8-46BC-83CF-6DFD9517F2DC} 159 | {B3CABFE5-36F6-4657-9E31-16DD822B0220} = {769CCC50-5EB4-40F7-86F2-F6228D0974D0} 160 | {3CEDC805-64E4-4E9C-A98A-6452F7F0F415} = {769CCC50-5EB4-40F7-86F2-F6228D0974D0} 161 | EndGlobalSection 162 | GlobalSection(ExtensibilityGlobals) = postSolution 163 | SolutionGuid = {4EDD1774-6287-4F9B-BB5A-24ADCDDCD1E8} 164 | EndGlobalSection 165 | EndGlobal 166 | -------------------------------------------------------------------------------- /FSharpWorkshop_Exercises.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgef/fsharpworkshop/f06b9e0089bb2d13e953b30fdcd1a1a01adf1698/FSharpWorkshop_Exercises.docx -------------------------------------------------------------------------------- /FSharpWorkshop_Exercises.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgef/fsharpworkshop/f06b9e0089bb2d13e953b30fdcd1a1a01adf1698/FSharpWorkshop_Exercises.pdf -------------------------------------------------------------------------------- /FSharpWorkshop_Slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgef/fsharpworkshop/f06b9e0089bb2d13e953b30fdcd1a1a01adf1698/FSharpWorkshop_Slides.pdf -------------------------------------------------------------------------------- /FSharpWorkshop_Slides.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorgef/fsharpworkshop/f06b9e0089bb2d13e953b30fdcd1a1a01adf1698/FSharpWorkshop_Slides.pptx -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Module1/Application/Application.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Module1/Application/Functions.fs: -------------------------------------------------------------------------------- 1 | module Functions 2 | 3 | open Types 4 | 5 | -------------------------------------------------------------------------------- /Module1/Application/Try.fsx: -------------------------------------------------------------------------------- 1 | #load "Types.fs" 2 | #load "Functions.fs" 3 | 4 | open Types 5 | open Functions 6 | 7 | -------------------------------------------------------------------------------- /Module1/Application/Types.fs: -------------------------------------------------------------------------------- 1 | module Types 2 | 3 | -------------------------------------------------------------------------------- /Module1/Demo.fsx: -------------------------------------------------------------------------------- 1 | // Demo 1 2 | 3 | let a = 1 4 | let b = 2 5 | let sum x y = x + y 6 | let res = sum a b 7 | 8 | let myTuple = (42, "hello") 9 | let number, message = myTuple 10 | 11 | type MyRecord = { Number: int; Message: string } 12 | let myRecord = { Number = 42; Message = "hello" } 13 | let newRecord = { myRecord with Message = "hi" } -------------------------------------------------------------------------------- /Module1/Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | module Tests 2 | 3 | open Xunit 4 | open Swensen.Unquote 5 | open Types 6 | open Functions 7 | 8 | //[] 9 | //let ``1-1 Create customer``() = 10 | // let customer = { Id = 1; IsVip = false; Credit = 0M } 11 | // test <@ customer.GetType () = typeof @> 12 | // 13 | //[] 14 | //let ``1-2 Promote to vip``() = 15 | // let customer = { Id = 1; IsVip = false; Credit = 0M } 16 | // let promotedCustomer = tryPromoteToVip (customer, 100.1M) 17 | // test <@ promotedCustomer.IsVip = true @> 18 | // 19 | //[] 20 | //let ``1-3 Do not promote to vip``() = 21 | // let customer = { Id = 1; IsVip = false; Credit = 0M } 22 | // let promotedCustomer = tryPromoteToVip (customer, 99.9M) 23 | // test <@ promotedCustomer.IsVip = false @> 24 | // 25 | //[] 26 | //let ``1-4 Get purchases for odd customers``() = 27 | // let customer = { Id = 1; IsVip = false; Credit = 0M } 28 | // let _, purchases = getPurchases customer 29 | // test <@ purchases = 80M @> 30 | // 31 | //[] 32 | //let ``1-5 Get purchases for even customers``() = 33 | // let customer = { Id = 2; IsVip = false; Credit = 0M } 34 | // let _, purchases = getPurchases customer 35 | // test <@ purchases = 120M @> -------------------------------------------------------------------------------- /Module1/Tests/Tests.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Module2/Application/Application.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Module2/Application/Functions.fs: -------------------------------------------------------------------------------- 1 | module Functions 2 | 3 | open Types 4 | 5 | let tryPromoteToVip purchases = 6 | let customer, amount = purchases 7 | if amount > 100M then { customer with IsVip = true } 8 | else customer 9 | 10 | let getPurchases customer = 11 | if customer.Id % 2 = 0 then (customer, 120M) 12 | else (customer, 80M) -------------------------------------------------------------------------------- /Module2/Application/Try.fsx: -------------------------------------------------------------------------------- 1 | #load "Types.fs" 2 | #load "Functions.fs" 3 | 4 | open Types 5 | open Functions 6 | 7 | let customer = { Id = 1; IsVip = false; Credit = 10M } 8 | 9 | let purchases = (customer, 101M) 10 | let vipCustomer = tryPromoteToVip purchases 11 | 12 | let calculatedPurchases = getPurchases customer -------------------------------------------------------------------------------- /Module2/Application/Types.fs: -------------------------------------------------------------------------------- 1 | module Types 2 | 3 | type Customer = { 4 | Id: int 5 | IsVip: bool 6 | Credit: decimal 7 | } -------------------------------------------------------------------------------- /Module2/Demo.fsx: -------------------------------------------------------------------------------- 1 | // Demo 1 2 | 3 | let a = 1 4 | let b = 2 5 | let sum x y = x + y 6 | let res = sum a b 7 | 8 | let myTuple = (42, "hello") 9 | let number, message = myTuple 10 | 11 | type MyRecord = { Number: int; Message: string } 12 | let myRecord = { Number = 42; Message = "hello" } 13 | let newRecord = { myRecord with Message = "hi" } 14 | 15 | 16 | // Demo 2 17 | 18 | let compute (x: int) (y: int) (operation: int -> int -> int) = operation x y 19 | let res' = compute 1 2 sum 20 | 21 | let addOne = sum 1 22 | let addTwo = sum 2 23 | 24 | let res1 = addOne 1 25 | let res2 = addTwo res1 26 | 27 | let res2' = 28 | 1 29 | |> addOne 30 | |> addTwo 31 | 32 | let addThree = addOne >> addTwo 33 | let res2'' = addThree 1 -------------------------------------------------------------------------------- /Module2/Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | module Tests 2 | 3 | open Xunit 4 | open Swensen.Unquote 5 | open Types 6 | open Functions 7 | 8 | //[] 9 | //let ``2-1 Increase min credit using id``() = 10 | // let customer = { Id = 1; IsVip = false; Credit = 0M } 11 | // let upgradedCustomer = increaseCredit (fun c -> c.Id = 2) customer 12 | // test <@ upgradedCustomer.Credit = 50M @> 13 | // 14 | //[] 15 | //let ``2-2 Increase max credit using id``() = 16 | // let customer = { Id = 2; IsVip = false; Credit = 0M } 17 | // let upgradedCustomer = increaseCredit (fun c -> c.Id = 2) customer 18 | // test <@ upgradedCustomer.Credit = 100M @> 19 | // 20 | //[] 21 | //let ``2-3 Increase credit keeping existing one``() = 22 | // let customer = { Id = 2; IsVip = false; Credit = 10M } 23 | // let upgradedCustomer = increaseCredit (fun c -> c.Id = 2) customer 24 | // test <@ upgradedCustomer.Credit = 110M @> 25 | // 26 | //[] 27 | //let ``2-4 Increase max credit using vip``() = 28 | // let customer = { Id = 2; IsVip = true; Credit = 0M } 29 | // let upgradedCustomer = increaseCreditUsingVip customer 30 | // test <@ upgradedCustomer.Credit = 100M @> 31 | // 32 | //[] 33 | //let ``2-5 Upgrade customer with even id``() = 34 | // let customer = { Id = 2; IsVip = false; Credit = 0M } 35 | // let upgradedCustomer = upgradeCustomer customer 36 | // test <@ upgradedCustomer.IsVip = true && upgradedCustomer.Credit = 100M @> 37 | // 38 | //[] 39 | //let ``2-6 Upgrade customer with odd id``() = 40 | // let customer = { Id = 1; IsVip = false; Credit = 0M } 41 | // let upgradedCustomer = upgradeCustomer customer 42 | // test <@ upgradedCustomer.IsVip = false && upgradedCustomer.Credit = 50M @> -------------------------------------------------------------------------------- /Module2/Tests/Tests.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Module3/Application/Application.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Module3/Application/Functions.fs: -------------------------------------------------------------------------------- 1 | module Functions 2 | 3 | open Types 4 | open System 5 | 6 | let tryPromoteToVip purchases = 7 | let customer, amount = purchases 8 | if amount > 100M then { customer with IsVip = true } 9 | else customer 10 | 11 | let getPurchases customer = 12 | if customer.Id % 2 = 0 then (customer, 120M) 13 | else (customer, 80M) 14 | 15 | let increaseCredit condition customer = 16 | if condition customer then { customer with Credit = customer.Credit + 100M } 17 | else { customer with Credit = customer.Credit + 50M } 18 | 19 | let increaseCreditUsingVip = increaseCredit (fun c -> c.IsVip) 20 | 21 | let upgradeCustomer = getPurchases >> tryPromoteToVip >> increaseCreditUsingVip -------------------------------------------------------------------------------- /Module3/Application/Try.fsx: -------------------------------------------------------------------------------- 1 | #load "Types.fs" 2 | #load "Functions.fs" 3 | 4 | open System 5 | open Types 6 | open Functions 7 | 8 | let customer = { 9 | Id = 1 10 | IsVip = false 11 | Credit = 0M 12 | PersonalDetails = Some { 13 | FirstName = "John" 14 | LastName = "Doe" 15 | DateOfBirth = DateTime(1970, 11, 23) 16 | } 17 | Notifications = ReceiveNotifications(receiveDeals = true, receiveAlerts = true) 18 | } 19 | 20 | let purchases = (customer, 101M) 21 | let vipCustomer = tryPromoteToVip purchases 22 | 23 | let calculatedPurchases = getPurchases customer 24 | 25 | let customerWithMoreCredit = customer |> increaseCredit (fun c -> c.IsVip) 26 | 27 | let upgradedCustomer = upgradeCustomer customer -------------------------------------------------------------------------------- /Module3/Application/Types.fs: -------------------------------------------------------------------------------- 1 | module Types 2 | 3 | open System 4 | 5 | type Customer = { 6 | Id: int 7 | IsVip: bool 8 | Credit: decimal 9 | } -------------------------------------------------------------------------------- /Module3/Demo.fsx: -------------------------------------------------------------------------------- 1 | // Demo 1 2 | 3 | let a = 1 4 | let b = 2 5 | let sum x y = x + y 6 | let res = sum a b 7 | 8 | let myTuple = (42, "hello") 9 | let number, message = myTuple 10 | 11 | type MyRecord = { Number: int; Message: string } 12 | let myRecord = { Number = 42; Message = "hello" } 13 | let newRecord = { myRecord with Message = "hi" } 14 | 15 | 16 | // Demo 2 17 | 18 | let compute (x: int) (y: int) (operation: int -> int -> int) = operation x y 19 | let res' = compute 1 2 sum 20 | 21 | let addOne = sum 1 22 | let addTwo = sum 2 23 | 24 | let res1 = addOne 1 25 | let res2 = addTwo res1 26 | 27 | let res2' = 28 | 1 29 | |> addOne 30 | |> addTwo 31 | 32 | let addThree = addOne >> addTwo 33 | let res2'' = addThree 1 34 | 35 | 36 | // Demo 3 37 | 38 | let divide x y = 39 | match y with 40 | | 0 -> None 41 | | _ -> Some(x/y) 42 | 43 | let result = divide 4 2 44 | let result' = divide 4 0 45 | 46 | type DivisionResult = 47 | | DivisionSuccess of result: int 48 | | DivisionError of message: string 49 | 50 | let divide' x y = 51 | match y with 52 | |0 -> DivisionError(message = "Divide by zero") 53 | |_ -> DivisionSuccess(result = x / y) 54 | 55 | let result'' = divide' 4 2 56 | let result''' = divide' 4 0 57 | 58 | [] type m; [] type km; [] type h 59 | let distanceInMts = 11580.0 60 | let distanceInKms = 87.34 61 | //let totalDistance = distanceInMts + distanceInKms // Error 62 | 63 | let convertToKms (mts: float) = 64 | let m = mts / 1.0 // remove unit of measure 65 | let k = m / 1000.0 // convert 66 | k * 1.0 // add new unit of measure 67 | 68 | let convertToKms' (mts: float) = mts / 1000.0 * 1.0 69 | 70 | let convertedToKms = convertToKms distanceInMts 71 | let totalDistance' = convertedToKms + distanceInKms 72 | let speed = totalDistance' / 2.4 -------------------------------------------------------------------------------- /Module3/Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | module Tests 2 | 3 | open System 4 | open Xunit 5 | open Swensen.Unquote 6 | open Types 7 | open Functions 8 | 9 | //let customer = { 10 | // Id = 1 11 | // IsVip = false 12 | // Credit = 0M 13 | // PersonalDetails = Some { 14 | // FirstName = "John" 15 | // LastName = "Doe" 16 | // DateOfBirth = DateTime(1970, 11, 23) } 17 | // Notifications = ReceiveNotifications(receiveDeals = true, 18 | // receiveAlerts = true) 19 | //} 20 | // 21 | //[] 22 | //let ``3-1 Create customer``() = 23 | // test <@ customer.GetType() = typeof @> 24 | // 25 | //[] 26 | //let ``3-2 Increase credit using USD``() = 27 | // let upgradedCustomer = increaseCreditUsingVip customer 28 | // test <@ upgradedCustomer.Credit = 50M @> 29 | // 30 | //[] 31 | //let ``3-3 Adult customer``() = 32 | // test <@ customer |> isAdult @> 33 | // 34 | //[] 35 | //let ``3-4 Non-adult customer``() = 36 | // let nonadult = { customer with PersonalDetails = Some { customer.PersonalDetails.Value with DateOfBirth = DateTime.Now.AddYears(-1) } } 37 | // test <@ not (nonadult |> isAdult) @> 38 | // 39 | //[] 40 | //let ``3-5 Customer without personal details``() = 41 | // let nonadult = { customer with PersonalDetails = None } 42 | // test <@ not (nonadult |> isAdult) @> 43 | // 44 | //[] 45 | //let ``3-6 Get alert when nofications are enabled``() = 46 | // let alert = customer |> getAlert 47 | // test <@ alert = "Alert for customer 1" @> 48 | // 49 | //[] 50 | //let ``3-7 Do not get alert when nofications are disabled``() = 51 | // let alert = { customer with Notifications = NoNotifications } |> getAlert 52 | // test <@ alert = "" @> -------------------------------------------------------------------------------- /Module3/Tests/Tests.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Module4/Application/Application.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Always 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Module4/Application/Data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "CustomerId": 1, 4 | "PurchasesByMonth": [ 60.0, 60.0, 60.0, 60.0, 60.0, 60.0, 60.0, 60.0, 60.0, 60.0, 60.0, 60.0 ] 5 | }, 6 | { 7 | "CustomerId": 2, 8 | "PurchasesByMonth": [ 150.60, 150.60, 150.60, 150.60, 150.60, 150.60, 150.60, 150.60, 150.60, 150.60, 150.60, 150.60 ] 9 | }, 10 | { 11 | "CustomerId": 3, 12 | "PurchasesByMonth": [ 80.30, 80.30, 80.30, 80.30, 80.30, 80.30, 80.30, 80.30, 80.30, 80.30, 80.30, 80.30 ] 13 | }, 14 | { 15 | "CustomerId": 4, 16 | "PurchasesByMonth": [ 130.20, 130.20, 130.20, 130.20, 130.20, 130.20, 130.20, 130.20, 130.20, 130.20, 130.20, 130.20 ] 17 | } 18 | ] -------------------------------------------------------------------------------- /Module4/Application/Functions.fs: -------------------------------------------------------------------------------- 1 | module Functions 2 | 3 | open Types 4 | open System 5 | 6 | let tryPromoteToVip purchases = 7 | let customer, amount = purchases 8 | if amount > 100M then { customer with IsVip = true } 9 | else customer 10 | 11 | let getPurchases customer = 12 | if customer.Id % 2 = 0 then (customer, 120M) 13 | else (customer, 80M) 14 | 15 | let increaseCredit condition customer = 16 | if condition customer then { customer with Credit = customer.Credit + 100M } 17 | else { customer with Credit = customer.Credit + 50M } 18 | 19 | let increaseCreditUsingVip = increaseCredit (fun c -> c.IsVip) 20 | 21 | let upgradeCustomer = getPurchases >> tryPromoteToVip >> increaseCreditUsingVip 22 | 23 | let isAdult customer = 24 | match customer.PersonalDetails with 25 | | None -> false 26 | | Some d -> d.DateOfBirth.AddYears 18 <= DateTime.Now.Date 27 | 28 | let getAlert customer = 29 | match customer.Notifications with 30 | | ReceiveNotifications(receiveAlerts = true) -> 31 | sprintf "Alert for customer %i" customer.Id 32 | | _ -> "" 33 | 34 | let getCustomer id = 35 | let customers = [ 36 | { Id = 1; IsVip = false; Credit = 0m; PersonalDetails = Some { FirstName = "John"; LastName = "Doe"; DateOfBirth = DateTime(1980, 1, 1) }; Notifications = NoNotifications } 37 | { Id = 2; IsVip = false; Credit = 10m; PersonalDetails = None; Notifications = ReceiveNotifications(true, true) } 38 | { Id = 3; IsVip = false; Credit = 30m; PersonalDetails = Some { FirstName = "Jane"; LastName = "Jones"; DateOfBirth = DateTime(2010, 2, 2) }; Notifications = ReceiveNotifications(true, false) } 39 | { Id = 4; IsVip = true; Credit = 50m; PersonalDetails = Some { FirstName = "Joe"; LastName = "Smith"; DateOfBirth = DateTime(1986, 3, 3) }; Notifications = ReceiveNotifications(false, true) } 40 | ] 41 | customers 42 | |> List.find (fun c -> c.Id = id) -------------------------------------------------------------------------------- /Module4/Application/Program.fs: -------------------------------------------------------------------------------- 1 | module Program 2 | 3 | open System 4 | open Services 5 | open Functions 6 | 7 | [] 8 | let rec main args = 9 | // let service = CustomerService() 10 | // printf "Id to upgrade [1-4]: " 11 | // let valid, id = Console.ReadLine () |> Int32.TryParse 12 | // printfn "" 13 | // if not valid then 14 | // printfn "Invalid customer Id" 15 | // else 16 | // printfn "Customer to upgrade:" 17 | // let customerBefore = getCustomer id 18 | // customerBefore |> service.GetCustomerInfo |> printfn "%s" 19 | // printfn "" 20 | // printfn "Upgrading customer..." 21 | // let customerAfter = service.UpgradeCustomer id 22 | // printfn "" 23 | // printfn "Customer upgraded:" 24 | // customerAfter |> service.GetCustomerInfo |> printfn "%s" 25 | printfn "" 26 | printfn "Press any key to try again or 'q' to quit" 27 | let input = Console.ReadKey () 28 | printfn "" 29 | if input.Key = ConsoleKey.Q then 0 else main args 30 | -------------------------------------------------------------------------------- /Module4/Application/Services.fs: -------------------------------------------------------------------------------- 1 | namespace Services 2 | -------------------------------------------------------------------------------- /Module4/Application/Try.fsx: -------------------------------------------------------------------------------- 1 | //#r @"/Users/[USERNAME]/.nuget/packages/fsharp.data/3.0.0-beta4/lib/netstandard2.0/FSharp.Data.dll" // Mac / Linux 2 | //#r @"%userprofile%\.nuget\packages\.nuget\packages\fsharp.data\3.0.0-beta4\lib\netstandard2.0\FSharp.Data.dll" // Windows 3 | #load "Types.fs" 4 | #load "Functions.fs" 5 | 6 | open System 7 | open Types 8 | open Functions 9 | 10 | let customer = { 11 | Id = 1 12 | IsVip = false 13 | Credit = 0M 14 | PersonalDetails = Some { 15 | FirstName = "John" 16 | LastName = "Doe" 17 | DateOfBirth = DateTime(1970, 11, 23) 18 | } 19 | Notifications = ReceiveNotifications(receiveDeals = true, receiveAlerts = true) 20 | } 21 | 22 | let purchases = (customer, 101M) 23 | let vipCustomer = tryPromoteToVip purchases 24 | 25 | let calculatedPurchases = getPurchases customer 26 | 27 | let customerWithMoreCredit = customer |> increaseCredit (fun c -> c.IsVip) 28 | 29 | let upgradedCustomer = upgradeCustomer customer 30 | 31 | let isAdultCustomer = isAdult customer 32 | 33 | let alertCustomer = getAlert customer 34 | -------------------------------------------------------------------------------- /Module4/Application/Types.fs: -------------------------------------------------------------------------------- 1 | module Types 2 | 3 | open System 4 | 5 | type PersonalDetails = { 6 | FirstName: string 7 | LastName: string 8 | DateOfBirth: DateTime 9 | } 10 | 11 | [] type EUR 12 | [] type USD 13 | 14 | type Notifications = 15 | | NoNotifications 16 | | ReceiveNotifications of receiveDeals: bool * receiveAlerts: bool 17 | 18 | type Customer = { 19 | Id: int 20 | IsVip: bool 21 | Credit: decimal 22 | PersonalDetails: PersonalDetails option 23 | Notifications: Notifications 24 | } -------------------------------------------------------------------------------- /Module4/Application/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Data -------------------------------------------------------------------------------- /Module4/Demo.fsx: -------------------------------------------------------------------------------- 1 | // Demo 1 2 | 3 | let a = 1 4 | let b = 2 5 | let sum x y = x + y 6 | let res = sum a b 7 | 8 | let myTuple = (42, "hello") 9 | let number, message = myTuple 10 | 11 | type MyRecord = { Number: int; Message: string } 12 | let myRecord = { Number = 42; Message = "hello" } 13 | let newRecord = { myRecord with Message = "hi" } 14 | 15 | 16 | // Demo 2 17 | 18 | let compute (x: int) (y: int) (operation: int -> int -> int) = operation x y 19 | let res' = compute 1 2 sum 20 | 21 | let addOne = sum 1 22 | let addTwo = sum 2 23 | 24 | let res1 = addOne 1 25 | let res2 = addTwo res1 26 | 27 | let res2' = 28 | 1 29 | |> addOne 30 | |> addTwo 31 | 32 | let addThree = addOne >> addTwo 33 | let res2'' = addThree 1 34 | 35 | 36 | // Demo 3 37 | 38 | let divide x y = 39 | match y with 40 | | 0 -> None 41 | | _ -> Some(x/y) 42 | 43 | let result = divide 4 2 44 | let result' = divide 4 0 45 | 46 | type DivisionResult = 47 | | DivisionSuccess of result: int 48 | | DivisionError of message: string 49 | 50 | let divide' x y = 51 | match y with 52 | |0 -> DivisionError(message = "Divide by zero") 53 | |_ -> DivisionSuccess(result = x / y) 54 | 55 | let result'' = divide' 4 2 56 | let result''' = divide' 4 0 57 | 58 | [] type m; [] type km; [] type h 59 | let distanceInMts = 11580.0 60 | let distanceInKms = 87.34 61 | //let totalDistance = distanceInMts + distanceInKms // Error 62 | 63 | let convertToKms (mts: float) = 64 | let m = mts / 1.0 // remove unit of measure 65 | let k = m / 1000.0 // convert 66 | k * 1.0 // add new unit of measure 67 | 68 | let convertToKms' (mts: float) = mts / 1000.0 * 1.0 69 | 70 | let convertedToKms = convertToKms distanceInMts 71 | let totalDistance' = convertedToKms + distanceInKms 72 | let speed = totalDistance' / 2.4 73 | 74 | 75 | // Demo 4 76 | 77 | let numbers = [1..100] 78 | let numbersWithZero = 0 :: numbers 79 | let evenNumbers = numbersWithZero |> List.filter (fun x -> x % 2 = 0) 80 | 81 | type MyClass(myField: int) = 82 | member this.MyProperty = myField 83 | member this.MyMethod methodParam = myField + methodParam 84 | 85 | let myInstance = MyClass(1) 86 | myInstance.MyProperty 87 | myInstance.MyMethod 2 88 | 89 | type IMyInterface = 90 | abstract member MyMethod: int -> int 91 | 92 | let myInstance' = 93 | { new IMyInterface with 94 | member this.MyMethod methodParam = 95 | methodParam + 1 } 96 | 97 | myInstance'.MyMethod 2 98 | 99 | //#r @"/Users/[USERNAME]/.nuget/packages/fsharp.data/3.0.0-beta4/lib/netstandard2.0/FSharp.Data.dll" // Mac / Linux 100 | //#r @"%userprofile%\.nuget\packages\.nuget\packages\fsharp.data\3.0.0-beta4\lib\netstandard2.0\FSharp.Data.dll" // Windows 101 | 102 | open FSharp.Data 103 | 104 | type Customer = JsonProvider<"Data.json"> 105 | let customers = Customer.Load "Data.json" 106 | 107 | customers 108 | |> Seq.iter (fun r -> printfn "%i: %A" r.CustomerId r.PurchasesByMonth) -------------------------------------------------------------------------------- /Module4/Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | module Tests 2 | 3 | open System 4 | open Xunit 5 | open Swensen.Unquote 6 | open Types 7 | open Functions 8 | open Services 9 | 10 | let customer = { 11 | Id = 1 12 | IsVip = false 13 | Credit = 0M 14 | PersonalDetails = Some { 15 | FirstName = "John" 16 | LastName = "Doe" 17 | DateOfBirth = DateTime(1970, 11, 23) } 18 | Notifications = ReceiveNotifications(receiveDeals = true, 19 | receiveAlerts = true) } 20 | 21 | 22 | //[] 23 | //let ``4-1 Get purchases average``() = 24 | // let purchases = getPurchases customer 25 | // test <@ purchases = (customer, 60M) @> 26 | // 27 | //[] 28 | //let ``4-2 Upgrade customer``() = 29 | // let service = CustomerService() 30 | // let upgradedCustomer = service.UpgradeCustomer 2 31 | // test <@ upgradedCustomer.IsVip @> 32 | // test <@ upgradedCustomer.Credit = 110M @> 33 | // 34 | //[] 35 | //let ``4-3 Get customer info``() = 36 | // let service = CustomerService() 37 | // let info = service.GetCustomerInfo customer 38 | // let expectedInfo = "Id: 1, IsVip: false, Credit: 0.00, IsAdult: true, Alert: Alert for customer 1" 39 | // test <@ info = expectedInfo @> -------------------------------------------------------------------------------- /Module4/Tests/Tests.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # F# Workshop 2 | 3 | ## Introduction 4 | 5 | **Do you want to learn F# and Functional Programming?** Well, you better start coding! 6 | 7 | Learning a new programming language is not easy, on top of reading a lot you need to practice even more. 8 | 9 | This workshop is designed to teach you some of the basics of F# and Functional Programming by combining theory ([slides](https://github.com/jorgef/fsharpworkshop/raw/master/FSharpWorkshop_Slides.pptx)) and practice ([exercises](https://github.com/jorgef/fsharpworkshop/raw/master/FSharpWorkshop_Exercises.pdf)). 10 | 11 | ## Modules 12 | 13 | The course is split into 4 modules, each of them contains a presentation (theory) and one exercise (practice). 14 | 15 | ### Module 1 16 | 17 | - Bindings 18 | - Functions 19 | - Tuples 20 | - Records 21 | 22 | ### Module 2 23 | 24 | - High order functions 25 | - Pipelining 26 | - Partial application 27 | - Composition 28 | 29 | ### Module 3 30 | 31 | - Options 32 | - Pattern matching 33 | - Discriminated unions 34 | - Units of measure 35 | 36 | ### Module 4 37 | 38 | - Functional lists 39 | - Object-oriented programming 40 | - Type providers 41 | 42 | ## Pre-requisites 43 | 44 | - [.NET Core SDK](https://www.microsoft.com/net/download) 45 | - [Visual Studio Code](https://code.visualstudio.com/) 46 | - [Ionide Package](http://ionide.io/) 47 | - [Mono (Mac only)](https://www.mono-project.com/download/stable/) 48 | - [Mono, F# Compiler and F# Interactive (Linux only)](https://fsharp.org/use/linux/) 49 | 50 | ## Workshop Feedback 51 | 52 | > "Awesome F# workshop by @jorgefioranelli" [@Sahebjade tweet](https://twitter.com/Sahebjade/status/1132234942333636609) 53 | 54 | > "Outstanding weekend #fsharp workshop led by @jorgefioranelli at @Jet's Hoboken HQ. #functionalprogramming #learning" [@JetTechnology tweet](https://twitter.com/JetTechnology/status/931957480258752513) 55 | 56 | > "Thanks @jorgefioranelli for the wonderful workshop " [@ckumareddy tweet](https://twitter.com/ckumareddy/status/931944429237231623) 57 | 58 | > "Great🆒workshop🔛 #code @Jet with @jorgefioranelli in functional #programming @fsharporg all #software #developers & #businessnews #businessdevelopment should ✔out" [@tomsnode tweet](https://twitter.com/tomsnode/status/931983517067640833) 59 | 60 | > "Thanks @jorgefioranelli for the awesome workshop at the NYC #fsharp lab hours. And thanks @JetTechnology for sponsoring." [@pblasucci tweet](https://twitter.com/pblasucci/status/734139405301075969) 61 | 62 | > "Thanks to @jorgefioranelli for teaching and @Valtech for hosting an awesome F# workshop last Saturday in London, learned a lot." [@pedromsantos tweet](https://twitter.com/pedromsantos/status/716903753442586624) 63 | 64 | > "Thanks @jorgefioranelli and @Valtech for today's amazing F# workshop. See you soon!" [@vgaltes tweet](https://twitter.com/vgaltes/status/716339992860164096) 65 | 66 | > "@DCFSharp with @jorgefioranelli running an awesome #fsharp workshop!" [@trikace tweet](https://twitter.com/TRikace/status/708679222441725953) 67 | 68 | > "DC F# Meetup - @jorgefioranelli 's F# Workshop - excellent intro to F#. Thanks!" [@cnromaine tweet](https://twitter.com/cnromaine/status/709380386124656641) 69 | 70 | > "Thanks to @jorgefioranelli for teaching and @CoStarGroup for hosting a great F# workshop this weekend! Learning a bunch." [@sjkilleen tweet](https://twitter.com/sjkilleen/status/708716443660111873) 71 | 72 | > "Thanks @jorgefioranelli for awesome #fsharp workshop!" [@grishace tweet](https://twitter.com/grishace/status/696073536595718144) 73 | 74 | > "Fantastic F# workshop today by @jorgefioranelli and @liammclennan - really clear, interesting, and enjoyable!" [@harrietgl tweet](https://twitter.com/harrietgl/status/600872399538532352) 75 | 76 | > "A truly excellent #F#Workshop yesterday by @jorgefioranelli . Thank you!" [@marcote_torres tweet](https://twitter.com/marcote_torres/status/598842148927201280) 77 | 78 | > "Check out @jorgefioranelli's #fsharp workshop, one of the best workshops I've attended! http://www.fsharpworkshop.com" [@fekberg tweet](https://twitter.com/fekberg/status/563477575230431234) 79 | 80 | > "Seriously impressed at the quality and professionalism of the #readifyfsharpworkshop by @jorgefioranelli and @tobycmoore" [@robdmoore tweet](https://twitter.com/robdmoore/status/532522727941218304) 81 | 82 | > "Can't praise @jorgefioranelli enough for the amazing outstanding preparation & delivery of his @readify #fsharp workshop tonight" [@meligy tweet](https://twitter.com/Meligy/status/514382840520138753) 83 | 84 | ## Recommended Learning Material 85 | 86 | Video: [Functional Programming: What? Why? When?](https://vimeo.com/97514630) by Robert C. Martin 87 | 88 | Video: [5 Reasons to Move from C# to F#](https://www.youtube.com/embed/-0BB3lU_qr4) by Jorge Fioranelli 89 | 90 | Practice Online: [dotnetfiddle](https://dotnetfiddle.net/) 91 | 92 | Book: [Real-World Functional Programming](http://www.manning.com/petricek/) by Tomas Petricek 93 | 94 | ## License 95 | 96 | Licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). 97 | 98 | Copyright 2018 Jorge Fioranelli 99 | --------------------------------------------------------------------------------