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