├── .gitignore ├── Functional Programming Patterns (45m).pptx ├── Functional Programming Patterns (DDD Melbourne).pptx ├── Functional Programming Patterns (NDC Sydney).pptx ├── Licence.md ├── Readme.md ├── args-example ├── Setup.hs ├── app │ └── Main.hs ├── args-example.cabal ├── src │ └── Lib.hs ├── stack.yaml └── test │ └── Spec.hs └── fparsec-example ├── .paket ├── paket.bootstrapper.exe ├── paket.exe ├── paket.exe.config └── paket.targets ├── App.config ├── ApplicativePhoneParser.fs ├── AssemblyInfo.fs ├── PhoneParser.fs ├── Program.fs ├── fparsec-example.fsproj ├── fparsec-example.sln ├── paket.dependencies ├── paket.lock └── paket.references /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work 2 | bin 3 | obj 4 | packages 5 | .vs -------------------------------------------------------------------------------- /Functional Programming Patterns (45m).pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel-chambers/FunctionalProgrammingPatterns/6c169a5db6d1843741e143659549de4ab80c9a00/Functional Programming Patterns (45m).pptx -------------------------------------------------------------------------------- /Functional Programming Patterns (DDD Melbourne).pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel-chambers/FunctionalProgrammingPatterns/6c169a5db6d1843741e143659549de4ab80c9a00/Functional Programming Patterns (DDD Melbourne).pptx -------------------------------------------------------------------------------- /Functional Programming Patterns (NDC Sydney).pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel-chambers/FunctionalProgrammingPatterns/6c169a5db6d1843741e143659549de4ab80c9a00/Functional Programming Patterns (NDC Sydney).pptx -------------------------------------------------------------------------------- /Licence.md: -------------------------------------------------------------------------------- 1 | The Functional Programming Patterns presentation and sample code is licenced under the [Creative Commons Attribution-ShareAlike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/) licence. 2 | 3 | ![Creative Commons License](https://i.creativecommons.org/l/by-sa/4.0/88x31.png) 4 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Functional Programming Patterns for Mere Mortals 2 | 3 | This repository contains the presentation materials and sample code for the Functional Programming Patterns presentation. Here's the abstract: 4 | 5 | > Have you ever peeked over the fence into functional programming land and gazed into a seemingly alien landscape of weird symbols and crazily named concepts? Has your curiosity about functional programming been stymied by complicated words and abstractions? If so, this talk is for you. 6 | 7 | > We’re going to take a practical, example-based journey through complex-sounding but deceptively simple functional patterns such as functors, applicatives and the big bad monad. We’ll see how these patterns work, what they’re for and how they are used to make clean, composable code. We’ll also identify the places where functional patterns are quietly being used in our mainstream day to day languages. By the end of the talk, you will be better equipped to take further steps down the functional programming path of enlightenment. 8 | 9 | There are two versions of the slides, one done for NDC Sydney 2017, which was a one hour talk, and a cut down 45 minute version for DDD Melbourne 2017. 10 | 11 | Video: [NDC Sydney 2017](https://www.youtube.com/watch?v=v9QGWbGppis) 12 | 13 | ## args-example 14 | To get the `args-example` sample code up and running, you will need: 15 | * [Haskell Stack][1] - to compile the code 16 | * [Atom][2] (optional) - to browse the Haskell code 17 | * [IDE-Haskell][3] (optional) - Atom plugin to support Haskell (make sure to read the Requirements section of their readme). To install GHC-Mod with Stack, use `stack build ghc-mod` inside the `args-example` directory. 18 | 19 | To build the example, run `stack install`. `args-example-exe` will then be on your path to execute. 20 | 21 | ## fparsec-example 22 | To get the `fparsec-example` sample code up and running, you will need: 23 | * Visual Studio 2015 (with Visual F# Tools) 24 | 25 | You can then build and run the project as normal. 26 | 27 | ## Recommended Reading 28 | Scott Wlashin's [FSharp for Fun and Profit][5] website contains many useful articles. Two specifically to look at are: 29 | * [Map and Bind and Apply, Oh my!][6] 30 | * [Railway Oriented Programming][7] 31 | 32 | The [Haskell Programming from First Principles][4] book is highly recommended as a comprehensive introduction to functional programming in Haskell, and has very complete chapters that walk you through functors, applicatives and monads. 33 | 34 | [1]: https://www.haskellstack.org/ 35 | [2]: https://atom.io/ 36 | [3]: https://github.com/atom-haskell/ide-haskell 37 | [4]: https://haskellbook.com 38 | [5]: https://fsharpforfunandprofit.com/ 39 | [6]: https://fsharpforfunandprofit.com/series/map-and-bind-and-apply-oh-my.html 40 | [7]: https://fsharpforfunandprofit.com/rop/ 41 | -------------------------------------------------------------------------------- /args-example/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /args-example/app/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Lib 4 | import Data.Monoid ((<>)) 5 | import Options.Applicative 6 | import Network.URI (URI, parseURI) 7 | 8 | data Configuration = 9 | Configuration 10 | { apiUri :: URI 11 | , dryRun :: Bool 12 | , verbose :: Bool } 13 | deriving (Show) 14 | 15 | apiUriOption :: Parser URI 16 | apiUriOption = 17 | option (maybeReader parseURI) 18 | ( long "api-uri" 19 | <> metavar "URI" 20 | <> help "The URI to the API" ) 21 | 22 | dryRunFlag :: Parser Bool 23 | dryRunFlag = 24 | flag False True 25 | ( short 'd' 26 | <> long "dry-run" 27 | <> help "Enables dry-run mode" ) 28 | 29 | verboseFlag :: Parser Bool 30 | verboseFlag = 31 | flag False True 32 | ( short 'v' 33 | <> long "verbose" 34 | <> help "Enables verbose logging" ) 35 | 36 | configurationParser :: Parser Configuration 37 | configurationParser = 38 | Configuration 39 | <$> apiUriOption -- map 40 | <*> dryRunFlag -- apply 41 | <*> verboseFlag -- apply 42 | 43 | main :: IO () 44 | main = do 45 | configuration <- execParser $ info (helper <*> configurationParser) fullDesc 46 | print configuration 47 | -------------------------------------------------------------------------------- /args-example/args-example.cabal: -------------------------------------------------------------------------------- 1 | name: args-example 2 | version: 0.1.0.0 3 | -- synopsis: 4 | -- description: 5 | homepage: https://github.com/githubuser/args-example#readme 6 | license: BSD3 7 | author: Author name here 8 | maintainer: example@example.com 9 | copyright: 2017 Author name here 10 | category: Web 11 | build-type: Simple 12 | cabal-version: >=1.10 13 | 14 | library 15 | hs-source-dirs: src 16 | exposed-modules: Lib 17 | build-depends: base >= 4.7 && < 5 18 | default-language: Haskell2010 19 | 20 | executable args-example-exe 21 | hs-source-dirs: app 22 | main-is: Main.hs 23 | ghc-options: -threaded -rtsopts -with-rtsopts=-N 24 | build-depends: base 25 | , args-example 26 | , optparse-applicative 27 | , network-uri 28 | default-language: Haskell2010 29 | 30 | test-suite args-example-test 31 | type: exitcode-stdio-1.0 32 | hs-source-dirs: test 33 | main-is: Spec.hs 34 | build-depends: base 35 | , args-example 36 | ghc-options: -threaded -rtsopts -with-rtsopts=-N 37 | default-language: Haskell2010 38 | 39 | source-repository head 40 | type: git 41 | location: https://github.com/githubuser/args-example 42 | -------------------------------------------------------------------------------- /args-example/src/Lib.hs: -------------------------------------------------------------------------------- 1 | module Lib 2 | ( someFunc 3 | ) where 4 | 5 | someFunc :: IO () 6 | someFunc = putStrLn "someFunc" 7 | -------------------------------------------------------------------------------- /args-example/stack.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # http://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # resolver: ghcjs-0.1.0_ghc-7.10.2 15 | # resolver: 16 | # name: custom-snapshot 17 | # location: "./custom-snapshot.yaml" 18 | resolver: lts-8.22 19 | 20 | # User packages to be built. 21 | # Various formats can be used as shown in the example below. 22 | # 23 | # packages: 24 | # - some-directory 25 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 26 | # - location: 27 | # git: https://github.com/commercialhaskell/stack.git 28 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 29 | # - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a 30 | # extra-dep: true 31 | # subdirs: 32 | # - auto-update 33 | # - wai 34 | # 35 | # A package marked 'extra-dep: true' will only be built if demanded by a 36 | # non-dependency (i.e. a user package), and its test suites and benchmarks 37 | # will not be run. This is useful for tweaking upstream packages. 38 | packages: 39 | - '.' 40 | # Dependency packages to be pulled from upstream that are not in the resolver 41 | # (e.g., acme-missiles-0.3) 42 | extra-deps: [] 43 | 44 | # Override default flag values for local packages and extra-deps 45 | flags: {} 46 | 47 | # Extra package databases containing global packages 48 | extra-package-dbs: [] 49 | 50 | # Control whether we use the GHC we find on the path 51 | # system-ghc: true 52 | # 53 | # Require a specific version of stack, using version ranges 54 | # require-stack-version: -any # Default 55 | # require-stack-version: ">=1.1" 56 | # 57 | # Override the architecture used by stack, especially useful on Windows 58 | # arch: i386 59 | # arch: x86_64 60 | # 61 | # Extra directories used by stack for building 62 | # extra-include-dirs: [/path/to/dir] 63 | # extra-lib-dirs: [/path/to/dir] 64 | # 65 | # Allow a newer minor version of GHC than the snapshot specifies 66 | # compiler-check: newer-minor -------------------------------------------------------------------------------- /args-example/test/Spec.hs: -------------------------------------------------------------------------------- 1 | main :: IO () 2 | main = putStrLn "Test suite not yet implemented" 3 | -------------------------------------------------------------------------------- /fparsec-example/.paket/paket.bootstrapper.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel-chambers/FunctionalProgrammingPatterns/6c169a5db6d1843741e143659549de4ab80c9a00/fparsec-example/.paket/paket.bootstrapper.exe -------------------------------------------------------------------------------- /fparsec-example/.paket/paket.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniel-chambers/FunctionalProgrammingPatterns/6c169a5db6d1843741e143659549de4ab80c9a00/fparsec-example/.paket/paket.exe -------------------------------------------------------------------------------- /fparsec-example/.paket/paket.exe.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /fparsec-example/.paket/paket.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | true 7 | $(MSBuildThisFileDirectory) 8 | $(MSBuildThisFileDirectory)..\ 9 | /Library/Frameworks/Mono.framework/Commands/mono 10 | mono 11 | 12 | 13 | 14 | 15 | $(PaketRootPath)paket.exe 16 | $(PaketToolsPath)paket.exe 17 | "$(PaketExePath)" 18 | $(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" 19 | 20 | 21 | 22 | 23 | 24 | $(MSBuildProjectFullPath).paket.references 25 | 26 | 27 | 28 | 29 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references 30 | 31 | 32 | 33 | 34 | $(MSBuildProjectDirectory)\paket.references 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | $(PaketCommand) restore --references-files "$(PaketReferences)" 47 | 48 | RestorePackages; $(BuildDependsOn); 49 | 50 | 51 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /fparsec-example/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /fparsec-example/ApplicativePhoneParser.fs: -------------------------------------------------------------------------------- 1 | module ApplicativePhoneParser 2 | 3 | open FParsec 4 | 5 | // Map 6 | let () fn f = f |>> fn 7 | 8 | // Apply 9 | let (<*>) (fn : Parser<('a -> 'b),'u>) (a : Parser<'a,'u>) : Parser<'b,'u> = 10 | fn >>= (fun fn' -> a >>= (fn' >> preturn)) 11 | 12 | type PhoneNumberType = 13 | | Victoria 14 | | NewSouthWales 15 | | Mobile 16 | 17 | type PhoneNumber = 18 | { areaCode : string 19 | number : string 20 | numberType : PhoneNumberType } 21 | 22 | let determineType areaCode = 23 | match areaCode with 24 | | "03" -> Some Victoria 25 | | "02" -> Some NewSouthWales 26 | | "04" -> Some Mobile 27 | | _ -> None 28 | 29 | let areaCode = 30 | manyMinMaxSatisfy 2 2 isDigit "2 digit area code" 31 | >>= fun areaCode -> 32 | match determineType areaCode with 33 | | Some numType -> preturn (areaCode, numType) 34 | | None -> fail <| sprintf "Invalid area code: %s" areaCode 35 | 36 | 37 | let areaCodeWithParens = 38 | (pchar '(') >>. areaCode .>> (pchar ')') 39 | 40 | let mainNumber = manyMinMaxSatisfy 8 8 isDigit "8 digit phone number" 41 | 42 | let phoneNumber = 43 | let parensAreaCode = attempt areaCodeWithParens <|> areaCode 44 | let mainNumberThenEof = mainNumber .>> eof 45 | let mkPhoneNumber (areaCode, numType) mainNumber = 46 | { areaCode = areaCode; number = mainNumber; numberType = numType; } 47 | 48 | // map apply 49 | mkPhoneNumber parensAreaCode <*> mainNumberThenEof 50 | 51 | let parsePhoneNumber str = 52 | run phoneNumber str -------------------------------------------------------------------------------- /fparsec-example/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace fparsec_example.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /fparsec-example/PhoneParser.fs: -------------------------------------------------------------------------------- 1 | module PhoneParser 2 | 3 | open FParsec 4 | open ApplicativePhoneParser 5 | 6 | let phoneNumber = 7 | let parensAreaCode = optional (pchar '(') >>. areaCode .>> optional (pchar ')') 8 | let mainNumberThenEof = mainNumber .>> eof 9 | let mkPhoneNumber (areaCode, numType) mainNumber = 10 | { areaCode = areaCode; number = mainNumber; numberType = numType; } 11 | pipe2 parensAreaCode mainNumberThenEof mkPhoneNumber 12 | 13 | let parsePhoneNumber str = 14 | run phoneNumber str -------------------------------------------------------------------------------- /fparsec-example/Program.fs: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org 2 | // See the 'F# Tutorial' project for more help. 3 | 4 | open ApplicativePhoneParser 5 | 6 | [] 7 | let main argv = 8 | match argv with 9 | | [|phoneNumberStr|] -> 10 | let phoneNumber = parsePhoneNumber phoneNumberStr 11 | printfn "%A" phoneNumber 12 | 0 13 | | _ -> 14 | printfn "Unknown args: %A" argv 15 | 1 16 | -------------------------------------------------------------------------------- /fparsec-example/fparsec-example.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | fparsec-example 5 | fparsec-example 6 | FParsecExample 7 | Debug 8 | AnyCPU 9 | 2.0 10 | 0ffbb9f9-afdc-42ff-be0e-517e1c1799b1 11 | Exe 12 | v4.6.2 13 | true 14 | 4.4.1.0 15 | 16 | 17 | true 18 | Full 19 | false 20 | false 21 | bin\$(Configuration)\ 22 | DEBUG;TRACE 23 | 3 24 | AnyCPU 25 | true 26 | 27 | 28 | PdbOnly 29 | true 30 | true 31 | bin\$(Configuration)\ 32 | TRACE 33 | 3 34 | AnyCPU 35 | true 36 | 37 | 38 | 39 | 40 | FSharp.Core 41 | $(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\FSharp\.NETFramework\v4.0\$(TargetFSharpCoreVersion)\FSharp.Core.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets 60 | 61 | 62 | 63 | 64 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | packages\FParsec\lib\net40-client\FParsec.dll 75 | True 76 | True 77 | 78 | 79 | packages\FParsec\lib\net40-client\FParsecCS.dll 80 | True 81 | True 82 | 83 | 84 | 85 | 86 | 87 | 88 | packages\FParsec\lib\portable-net45+netcore45+wpa81+wp8\FParsec.dll 89 | True 90 | True 91 | 92 | 93 | packages\FParsec\lib\portable-net45+netcore45+wpa81+wp8\FParsecCS.dll 94 | True 95 | True 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | packages\System.ValueTuple\lib\netstandard1.0\System.ValueTuple.dll 105 | True 106 | True 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /fparsec-example/fparsec-example.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.16 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{C06D5BDC-85ED-41E7-818D-DFF6F07F511C}" 7 | ProjectSection(SolutionItems) = preProject 8 | paket.dependencies = paket.dependencies 9 | EndProjectSection 10 | EndProject 11 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "fparsec-example", "fparsec-example.fsproj", "{0FFBB9F9-AFDC-42FF-BE0E-517E1C1799B1}" 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {0FFBB9F9-AFDC-42FF-BE0E-517E1C1799B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {0FFBB9F9-AFDC-42FF-BE0E-517E1C1799B1}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {0FFBB9F9-AFDC-42FF-BE0E-517E1C1799B1}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {0FFBB9F9-AFDC-42FF-BE0E-517E1C1799B1}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | EndGlobal 28 | -------------------------------------------------------------------------------- /fparsec-example/paket.dependencies: -------------------------------------------------------------------------------- 1 | source http://www.myget.org/f/aspnetwebstacknightly/ 2 | source http://www.myget.org/f/aspnetwebstacknightlyrelease/ 3 | source https://www.nuget.org/api/v2/ 4 | 5 | nuget System.ValueTuple 4.3.0 restriction: >= net462 6 | nuget fparsec -------------------------------------------------------------------------------- /fparsec-example/paket.lock: -------------------------------------------------------------------------------- 1 | NUGET 2 | remote: https://www.nuget.org/api/v2 3 | FParsec (1.0.2) 4 | System.ValueTuple (4.3) - restriction: >= net462 5 | -------------------------------------------------------------------------------- /fparsec-example/paket.references: -------------------------------------------------------------------------------- 1 | System.ValueTuple 2 | fparsec --------------------------------------------------------------------------------