├── .gitignore ├── 1.1 Arrows as Functions ├── README.md ├── fog.csx └── fog.fsx ├── 1.2 Properties of Composition ├── README.md ├── associativity.csx └── associativity.fsx ├── 2.6 Examples of Types ├── README.md ├── ignore and discard.csx └── ignore and discard.fsx ├── 3.4 Monoid as Set ├── README.md ├── monoid.csx └── monoid.fsx ├── 4 Kleisli Categories ├── README.md ├── kleisli.csx └── kleisli.fsx ├── 5 Products and Coproducts ├── README.md ├── pandcop.csx └── pandcop.fsx ├── 6 Simple Algebraic Data Types ├── 6.1 Product Types.csx ├── 6.1 Product Types.fsx ├── 6.2 Records.csx ├── 6.2 Records.fsx ├── 6.3 Sum Types.csx ├── 6.3 Sum Types.fsx ├── 6.4 Algebra Types.csx ├── 6.4 Algebra Types.fsx └── README.md ├── 7 Functor ├── 7.1.1 The Maybe Functor.csx ├── 7.1.1 The Maybe Functor.fsx ├── 7.1.6 The List Functor.csx ├── 7.1.6 The List Functor.fsx ├── 7.1.7 The Reader Functor.csx ├── 7.1.7 The Reader Functor.fsx ├── 7.2 Functors as Containers.csx ├── 7.2 Functors as Containers.fsx ├── 7.3 Functor Composition.csx ├── 7.3 Functor Composition.fsx └── README.md ├── 8 Functoriality ├── 8.1 Bifunctors.csx ├── 8.1 Bifunctors.fsx ├── 8.2 Product and Coproduct Bifunctors.fsx ├── 8.3 Functorial Algebraic Data Types.fsx ├── 8.4 Functors in C#.csx ├── 8.4 Functors in F#.fsx ├── 8.5 The Writer Functor.fsx ├── 8.6 Covariant and Contravariant Functors.fsx └── README.md ├── LICENSE └── README.md /.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 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /1.1 Arrows as Functions/README.md: -------------------------------------------------------------------------------- 1 | # Function Composition 2 | 3 | f and g are little string functions that display f(x) or g(x) where x is a string value. 4 | It is easy to see in REPL style the order : f(g(x)) or g(f(x)). 5 | -------------------------------------------------------------------------------- /1.1 Arrows as Functions/fog.csx: -------------------------------------------------------------------------------- 1 | static class Composition 2 | { 3 | public static Func fog(Func f, Func g) { 4 | return x => g(f(x)); 5 | } 6 | 7 | ///The main difference with previous : it returns a value and the type inference works. 8 | ///But we cannot separate composition and execution. This notion is important because program is composition THEN execution 9 | public static R2 fog2(Func f, Func g, T1 x) { 10 | return g(f(x)); 11 | } 12 | } 13 | 14 | static class Functions 15 | { 16 | public static string f (string x) => System.String.Format($"f({x})"); 17 | public static string g (string x) => System.String.Format($"g({x})"); 18 | } 19 | 20 | //The type inference doesn't help us. We have to provide function types. 21 | Func fog = Composition.fog (Functions.f, Functions.g); 22 | System.Console.WriteLine(fog ("x")); 23 | 24 | //Type inference is quite limited when method returns function. Type inference works well with value. 25 | var fogx = Composition.fog2 (Functions.f, Functions.g, "x"); 26 | 27 | System.Console.WriteLine(fogx); 28 | 29 | -------------------------------------------------------------------------------- /1.1 Arrows as Functions/fog.fsx: -------------------------------------------------------------------------------- 1 | let f x = sprintf "f(%s)" x 2 | let g x = sprintf "g(%s)" x 3 | 4 | //The book introduce the left to right function composition with the >> operator. 5 | //To be mathematics/Haskell compliant, let's use the << operator 6 | let fog = f << g 7 | 8 | //Here the function composition is right to left 9 | fog "x" = "f(g(x))" 10 | (f >> g) "x" = "g(f(x))" -------------------------------------------------------------------------------- /1.2 Properties of Composition/README.md: -------------------------------------------------------------------------------- 1 | # Associativity property 2 | 3 | Addition operation is associative : 1 + 2 = 2 + 1. 4 | 5 | # Identity and zero : 6 | 7 | You can define identity by adding zero (neutral value) : 8 | 9 | 1 + 0 = 1 and 0 + 1 = 1 10 | 11 | Why : here is an excellent use case (flatten and more) with LINQ, : https://stackoverflow.com/questions/1466689/linq-identity-function -------------------------------------------------------------------------------- /1.2 Properties of Composition/associativity.csx: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | public static class Functions { 5 | /// Add one function 6 | public static int f (int x) => x + 1; 7 | /// Add 2 8 | public static int g (int x) => x + 2; 9 | 10 | public static int identity (int x) => x + 0; 11 | 12 | public static T id (T x) => x; 13 | } 14 | 15 | //add functions is Associative op 16 | Console.WriteLine(Functions.f(Functions.g(1)) == Functions.g(Functions.f(1))); //True 17 | 18 | //adding 0 in addition operation is identity because 0 is the neutral value. 19 | Console.WriteLine(Functions.identity (1) == Functions.id(1)); //True 20 | 21 | //Other example : flatten 22 | 23 | var listOfList = new[] { new[]{1,2}, new[]{3,4} }; 24 | 25 | //Identity is everywhere in csharp but implicit thanks to lambda. 26 | var flattenList = listOfList.SelectMany(x => x); //{ 1, 2, 3, 4 } 27 | 28 | //using Functions; can't do that in csx, you have to open namespace first.. It would be nice to do that in order to have a shorter id function. 29 | 30 | //We can define our own id function in csharp but in this sample it is longer than the lambda one. 31 | var flattenListWithIdentity = listOfList.SelectMany(Functions.id); //{ 1, 2, 3, 4 } 32 | -------------------------------------------------------------------------------- /1.2 Properties of Composition/associativity.fsx: -------------------------------------------------------------------------------- 1 | ///Associativity with addition samples 2 | 3 | //f is add one function 4 | let f x = x + 1 5 | 6 | //g is add 2 function 7 | let g x = x + 2 8 | 9 | let fog = f << g 10 | let gof = g << f 11 | 12 | fog 1 = 4 //true 13 | gof 1 = 4 //true 14 | 15 | (f << g) 1 = (g << f) 1 16 | 17 | //Identity in addition sample 18 | 19 | (f << id) 1 = 2 20 | (id << f) 1 = 2 21 | 22 | (f << id) 1 = (id << f) 1 23 | 24 | let neutral = 0 25 | 26 | let identity = (+) neutral // aka let identity x = x + 0 27 | 28 | //In fsharp, like Haskell, an identity function is already defined named 'id' 29 | identity 1 = id 1 //true 30 | 31 | //Identity can be useful to concat list : 32 | 33 | [1;2;3;4] = ([[1;2];[3;4]] |> List.collect id) //List.concat already exists for that. -------------------------------------------------------------------------------- /2.6 Examples of Types/README.md: -------------------------------------------------------------------------------- 1 | # Examples of types 2 | 3 | ## Introducing Void and unit 4 | 5 | Why void cannot but used as unit in csharp and why unit is useful. 6 | 7 | For compatibility, unit type in fsharp ise converted to void type in csharp (for interop). 8 | 9 | In fsharp you can write : ignore 1 = ignore 1, the code compiles but in csharp you can't compare/use void type. 10 | The type unit does not really exists in csharp. The strange things in csharp is the no parameter method definition : it ends with '()' which is unit. 11 | 12 | ## Concrete sample in .Net where unit is needed. 13 | Some aspect oriented programming or mock framework libs (like Moq, RhinoMocks, ...) have defined a Void or Unit type to simplify reflection. 14 | 15 | ```Action could be Action and Action could be Func``` 16 | 17 | In mock framework you may have at least 3 overloads to mock a call.. Having only 1 call helps to avoid overloading in favor of type inference. 18 | Overloading is a feature but also a limitation for the type inference system: the developper should choose one of them. 19 | 20 | With this little convention only 1 type instead of 3 is needed to start with reflection. 21 | You understand now why this difference make sense when you want to compose a program. 22 | By reducing the number of type to build the same things, you can have a more powerfull tool to compose program. -------------------------------------------------------------------------------- /2.6 Examples of Types/ignore and discard.csx: -------------------------------------------------------------------------------- 1 | static class Functions 2 | { 3 | /// unit is a kind of void in csharp but you can't use it as parameter.. 4 | public static void ignore(T x) { return; } 5 | } 6 | 7 | Functions.ignore(1); //ok 8 | 9 | // /!\ This code does not compile and this is why void could not be used as unit. 10 | Functions.ignore(1); == Functions.ignore(1); -------------------------------------------------------------------------------- /2.6 Examples of Types/ignore and discard.fsx: -------------------------------------------------------------------------------- 1 | let ignoreValue _ = () 2 | 3 | ignoreValue 1 4 | 5 | //In Fsharp this function already exists : 'ignore' 6 | 7 | ignore 1 = ignoreValue 1 //true -------------------------------------------------------------------------------- /3.4 Monoid as Set/README.md: -------------------------------------------------------------------------------- 1 | # Monoid as Set 2 | 3 | Monoid is just a way to provide the porcelain and plumbing parts to traverse/fold the structure easily (an empty/neutral/zero value and an operation like 0 and + for the addition sample of the previous chapter). 4 | 5 | ## String Concatenation sample 6 | In CSharp, we can check that default LINQ aggregation without intial seed fails on empty list. 7 | But if we use the Aggregate with intial seed, there is no problem to handle the empty list. 8 | If you want to build better app that will not crash on empty list, you can use this one. 9 | The monoid is here: To have a TOTAL aggregate function over the list, we have to supply 2 things : the initial seed (mempty) and the aggregate function (mappend). 10 | So we already have a monoid in csharp over enumerable but it is implicit. 11 | 12 | In fsharp sample, list module does not provide a implicit non empty aggregate function. 13 | That way you avoid to crash implicitly your app on empty list by design. 14 | Fold in fsharp is TOTAL by design. 15 | 16 | This kind of bug is like null reference exception. 17 | Before dotnet nullable reference type we have no garantee for reference type if the instance is null or not. 18 | 19 | This is why using Monoid can help you to build a better app. 20 | 21 | Monoid can be helpful for async operation, optional value and so on... -------------------------------------------------------------------------------- /3.4 Monoid as Set/monoid.csx: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | //An example of implementation by using interface because there is nor Trait nor static interface in CSharp 6 | interface Monoid 7 | { 8 | T mempty(); 9 | T mappend(T x, T Y); 10 | } 11 | 12 | class StringM : Monoid 13 | { 14 | public string mempty() => ""; 15 | public string mappend(string x, string y) => x + y; 16 | } 17 | 18 | var stringM = new StringM(); 19 | 20 | var welcome = stringM.mappend("hello", " world"); 21 | 22 | //Monoid : a foldable/traversable structure; like aggregating in LINQ enumerable after all.. 23 | 24 | var words = new[] { "hello", " ", "world" }; 25 | 26 | //In this case, the first element of our list is the seed and it works... 27 | var r = words.Aggregate((state, x) => stringM.mappend(state, x)); 28 | 29 | //... but what about empty list ? it fails with : Sequence contains no elements! So why providing default implementation for non empty list with no garantee ? 30 | var r2 = new List().Aggregate((state, x) => stringM.mappend(state, x)); 31 | 32 | // A better one : providing the initial seed : the neutral, the mempty of our Monoid 33 | var r3 = new List().Aggregate(stringM.mempty(), stringM.mappend); 34 | -------------------------------------------------------------------------------- /3.4 Monoid as Set/monoid.fsx: -------------------------------------------------------------------------------- 1 | // In fsharp and in dotnet, there is nor trait nor type class. 2 | // It is implicit and you have to provide functions by yourself 3 | 4 | module String = 5 | let mempty = "" 6 | let mappend x y = sprintf "%s%s" x y 7 | 8 | //our aggregate function in fsharp without implicit first element as initial seed. 9 | //You can provide traverse function from list to string like this : 10 | let ofList = List.fold mappend mempty //We are using partial application on the list 11 | 12 | 13 | String.mappend "hello" " world" = (String.ofList ["hello"; " "; "world"]) 14 | 15 | //In fsharp, the list does not provide a function with implicit zero as head, 16 | //you have to provide the neutral by yourself. The monoid is implicit but necessary when you fold/traverse structure -------------------------------------------------------------------------------- /4 Kleisli Categories/README.md: -------------------------------------------------------------------------------- 1 | # 4 Kleisli Categories 2 | 3 | The Writer example (log every function call) is useful to hide the log for the caller (in the function signature). 4 | Only adapted functions return a string (the log) inside the Writer. 5 | To adapt 2 functions, we need a kleisli composition (fish operator) to use standard function (upper and words) inside the Writer. 6 | 7 | ## Personal notes (not in this book, but useful I think to get it) 8 | https://softwareengineering.stackexchange.com/questions/165356/equivalent-of-solid-principles-for-functional-programming/171534 9 | 10 | Kleisli composition can help you if you already are a [SOLID principles](https://en.wikipedia.org/wiki/SOLID) lover. -------------------------------------------------------------------------------- /4 Kleisli Categories/kleisli.csx: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | static class Functions 4 | { 5 | public static string upper(string x) => x.ToUpperInvariant(); 6 | public static string[] words(string x) => x.Split(' '); 7 | } 8 | 9 | class Writer : Tuple 10 | { 11 | public Writer(T x, string message) : base(x, message) { } 12 | } 13 | 14 | class ExplainedFunctions 15 | { 16 | public static Writer toUpper(string x) => new Writer(Functions.upper(x), "toUpper "); 17 | public static Writer toWords(string x) => new Writer(Functions.words(x), "toWords "); 18 | 19 | public static Writer identity(T x) => new Writer(x, ""); 20 | 21 | public static Writer process(string x) 22 | { 23 | //tuple deconstruct fails with a type inference issue.. 24 | //var (y, l1) = toUpper(x); 25 | var y = toUpper(x); 26 | var z = toWords(y.Item1); 27 | 28 | return new Writer(z.Item1, y.Item2 + z.Item2); 29 | } 30 | } 31 | 32 | static class Kleisli 33 | { 34 | public static Func> Compose(Func> f, Func> g) 35 | { 36 | Writer composition(T1 x) 37 | { 38 | Writer y = f(x); 39 | Writer z = g(y.Item1); 40 | return new Writer(z.Item1, y.Item2 + z.Item2); 41 | } 42 | return composition; 43 | } 44 | } 45 | 46 | //Here the composition part is hard to write in CSharp due to type inference issue. 47 | //We have to force the type and loose inference, causing less benefits of function composition pattern. 48 | var composition = Kleisli.Compose(ExplainedFunctions.toUpper, ExplainedFunctions.toWords); 49 | var r1 = composition("hello world"); 50 | var r2 = ExplainedFunctions.process("hello world"); 51 | var compositionWithIdentity = Kleisli.Compose(composition, ExplainedFunctions.identity); 52 | var r3 = compositionWithIdentity("hello world"); 53 | //r1 = r2 = r3. -------------------------------------------------------------------------------- /4 Kleisli Categories/kleisli.fsx: -------------------------------------------------------------------------------- 1 | let upper x = (x:string).ToUpperInvariant() 2 | let words s = (s:string).Split(' ') 3 | 4 | //If we want to explain what we are doing with a log, we can use pair to get the log : 5 | 6 | type Writer<'a> = Writer of 'a * string 7 | 8 | //Explained functions 9 | let toUpper x = Writer (upper x, "toUpper ") 10 | let toWords x = Writer (words x, "toWords ") 11 | let identity x = Writer (x, "") 12 | 13 | //Composition 14 | let process' x = 15 | let (Writer (y, l1)) = toUpper x 16 | let (Writer (z, l2)) = toWords y 17 | Writer (z, l1 + l2) 18 | 19 | process' "hello world" 20 | 21 | //How to compose more than 2 explained functions ? 22 | 23 | //Here is the Kleili composition. It is like the process' function excepts that function are supplied as parameter. 24 | module Writer = 25 | module Operators = 26 | let (>=>) f g = 27 | fun x -> 28 | let (Writer (y, l1)) = f x 29 | let (Writer (z, l2)) = g y 30 | Writer (z, l1 + l2) 31 | 32 | open Writer.Operators 33 | 34 | //Here is our final function composition. We can add other function easily with the fish operator 35 | let composition = toUpper >=> toWords 36 | 37 | composition "hello world" = process' "hello world" //true 38 | 39 | (toUpper >=> toWords) "hello world" = (toUpper >=> toWords >=> identity) "hello world" -------------------------------------------------------------------------------- /5 Products and Coproducts/README.md: -------------------------------------------------------------------------------- 1 | # Products and Coproducts 2 | 3 | This chapter cover very interesting properties about morphims before introducing Products. 4 | 5 | In very short : 6 | > Empty set as Void like an initial object 7 | 8 | > Singleton as unit () like a terminal object 9 | 10 | > Duality, the heart of product and coproduct 11 | 12 | > Isomophism and so on... 13 | 14 | We will introduce product factorizers and finally coproducts. 15 | This chapter explain very well this principles and I will not explain it better than the author. 16 | 17 | The end of this chapter is interesting by exposing differences between product and coproduct properties (bijective or not) 18 | -------------------------------------------------------------------------------- /5 Products and Coproducts/pandcop.csx: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | //those function is helpful and avoid lambda deconstruction. 6 | //Because constructing a tuple is like passing all parameters to function, it is harder to deconstruct tuple in method argument.. 7 | static class Functions 8 | { 9 | public static T1 fst(ValueTuple t) 10 | { 11 | var (x, _) = t; 12 | return x; 13 | } 14 | 15 | public static T2 snd(ValueTuple t) 16 | { 17 | var (_, y) = t; 18 | return y; 19 | } 20 | 21 | //Do you understand why WITHOUT type inference on function it is hard to define those things ?! 22 | public static ValueTuple, ValueTuple> factorizer(Func, T1> f, Func, T2> g, ValueTuple x) 23 | { 24 | //Without inference, the signature is larger than the implementation.. 25 | return (x, (f(x), g(x))); 26 | } 27 | } 28 | 29 | // construct a pair like fsharp 30 | var pair = (1, true); 31 | 32 | var (x1, y2) = pair; 33 | 34 | // /!\ This code does not compile because constructing a pair and passing as argument is different. 35 | // It is an issue to use pair instead of out parameter for example. 36 | var x = Functions.fst(pair); 37 | var y = Functions.snd((1, true)); 38 | 39 | var r = Functions.factorizer(Functions.fst, Functions.snd, pair); 40 | 41 | 42 | //Type as set with list sample : 43 | 44 | class TypeAsSet 45 | { 46 | public static int[] surjective(int x) => new[] { x, 2 * x }; 47 | public static int[] injective() => new[] { 1, 2 }; 48 | } 49 | 50 | //surjective or onto : domain size is lower than codomain. One element of the domain map n elements of the codomain 51 | var surjective = new[] { 1 }.SelectMany(TypeAsSet.surjective); 52 | 53 | //injective or one-to-one : unit map multiple element of the codomain 54 | var injective = TypeAsSet.injective(); 55 | 56 | //bijection 57 | class Functions2 58 | { 59 | public static int f(int x) => x + 1; 60 | public static int cof(int x) => x - 1; 61 | } 62 | var l = new[] { 1, 2 }; 63 | var l2 = l.Select(x => Functions2.cof(Functions2.f(x))); // [1; 2], l2 == l 64 | 65 | injective() 66 | -------------------------------------------------------------------------------- /5 Products and Coproducts/pandcop.fsx: -------------------------------------------------------------------------------- 1 | //The simplest product is pair of types : 2 | 3 | let pair = 1, true 4 | 5 | //Here is 2 functions that extract the first or second part 6 | 7 | let first (x,_) = x 8 | 9 | let second (_,y) = y 10 | 11 | first pair = 1 //true 12 | second pair = true //true 13 | 14 | //Those functions already exists as fst and snd like Haskell 15 | fst pair = first pair //true 16 | snd pair = second pair 17 | 18 | //Now how can I reverse the first function to our initial pair ? 19 | 20 | (fst pair |> fun x -> x, true) = pair //true 21 | //this previous sample works because I already know that the second value is true. 22 | //To do that correctly we have to not loss information. This is the aim of factorizers : 23 | 24 | let factorizer f g x = x, (f x, g x) 25 | 26 | let pair_factorizer = factorizer fst snd 27 | 28 | pair_factorizer pair |> fst = pair //true 29 | let pair2 = 10, false 30 | pair_factorizer pair2 |> fst = pair2 //true 31 | //So now it works because we are maintening the initial value. 32 | //Now we are able to write coproduct of pair thanks to our pair_factorizer (coproduct is the dual operation of product) 33 | 34 | 35 | //Type as set with list sample : 36 | 37 | //surjective or onto domain : function that use less space of the codomain like having a singleton in list. 38 | let surjective x = List.singleton x 39 | surjective 1 40 | 41 | //injective or one-to-one : unit map multiple element of the codomain 42 | let injective () = [ 1;2 ] 43 | injective () 44 | 45 | //bijection 46 | let f x = x + 1 47 | let cof x = x - 1 48 | let l = [1;2] 49 | l |> List.map (f >> cof) = l 50 | 51 | -------------------------------------------------------------------------------- /6 Simple Algebraic Data Types/6.1 Product Types.csx: -------------------------------------------------------------------------------- 1 | //Pair is not commutative 2 | var pair1 = (1, true); 3 | var pair2 = (true, 1); 4 | 5 | sealed class Unit 6 | { 7 | private Unit() 8 | { 9 | 10 | } 11 | 12 | public static Unit Singleton = new Unit(); 13 | } 14 | 15 | static class Functions 16 | { 17 | //We can provide a swap function that reverse the pair 18 | public static ValueTuple swap(ValueTuple t) 19 | { 20 | var (x, y) = t; 21 | return (y, x); 22 | } 23 | public static ValueTuple, T3> alpha(ValueTuple> t) 24 | { 25 | var (x, (y, z)) = t; 26 | return ((x, y), z); 27 | } 28 | public static ValueTuple> alpha_inv(ValueTuple, T3> t) 29 | { 30 | var ((x, y), z) = t; 31 | return (x, (y, z)); 32 | } 33 | 34 | public static T rho(ValueTuple t) 35 | { 36 | var (x, _) = t; 37 | return x; 38 | } 39 | public static ValueTuple rho_inv(T x) => (x, Unit.Singleton); 40 | public static ValueTuple P (string s, bool b) => (s, b); 41 | public static ValueTuple p(T1 x, T2 y) => (x, y); 42 | public static Func partial_app (Func f, T1 x) => (T2 y) => f(x, y); 43 | } 44 | 45 | // /!\ Execute this script line by line to switch from statement to expression (an expression returns a value displayed into the console) 46 | //Main issue in csx : everything is statement. If it was expression instead, a value would have been displayed in the the console. 47 | //So if I remove the ';' at the end it is a kind of expression in csx and the value is displayed but multiple lines fail at compile/design time because the ';' at the end is missing ?! 48 | pair1 == Functions.swap(pair2) //true in type and by value thanks to swap 49 | 50 | pair1 == Functions.swap(Functions.swap(pair1)) //true 51 | 52 | //Isomorphism as associativity law in monoids. 53 | Functions.alpha_inv(Functions.alpha(("a", ("b", "c")))) == ("a", ("b", "c")) //true 54 | 55 | //Now check the zero one : 56 | 57 | Functions.rho(Functions.rho_inv( 1 )) == 1 //true 58 | 59 | 60 | //Pair as single case 61 | var stmt = Functions.P("This statements is", false); 62 | 63 | // Personal notes 64 | //link : https://en.wikipedia.org/wiki/Currying 65 | //link : http://blog.ploeh.dk/2017/01/30/partial-application-is-dependency-injection/ 66 | //Partial application is hard here because multiple arguments of function is ambiguous with tuple because you can't generate function with n-1 parameters 67 | //Partial application can replace dependency injection frameworks (dependency injection with partial application) and could reduce complexity in frameworks (WCF, Kestrel and so on..) 68 | 69 | //We can adapt by building our own partial_app function but due to type inference issue it is hard to keep track of types. 70 | var p2 = Functions.partial_app>(Functions.p, "Hello"); 71 | 72 | var stmt2 = p2(false); 73 | 74 | -------------------------------------------------------------------------------- /6 Simple Algebraic Data Types/6.1 Product Types.fsx: -------------------------------------------------------------------------------- 1 | //Pair is not commutative 2 | let pair1 = (1, true) 3 | let pair2 = (true, 1) 4 | 5 | //We can provide a swap function that reverse the pair 6 | let swap (x, y) = (y, x) 7 | 8 | pair1 = swap pair2 //true in type and by value thanks to swap 9 | 10 | pair1 = (swap >> swap) pair1 //true 11 | 12 | let alpha (x, (y, z)) = ((x, y), z) 13 | let alpha_inv ((x, y), z) = (x, (y, z)) 14 | 15 | //Isomorphism as associativity law in monoids. 16 | (alpha >> alpha_inv) ("a", ("b", "c")) = ("a", ("b", "c")) //true 17 | 18 | //Now check the zero one : 19 | let rho (x, ()) = x 20 | let rho_inv x = x, () 21 | 22 | (rho_inv >> rho) 1 = 1 //true 23 | 24 | 25 | //Pair as single case 26 | 27 | type Pair<'a, 'b> = P of 'a * 'b 28 | 29 | let stmt = P ("This statements is", false) 30 | 31 | //link : https://en.wikipedia.org/wiki/Currying 32 | //like haskell function with 2 arguments instead of having one tuple arg. Better in case of partial application. 33 | let p x y = P (x, y) 34 | let stmt2 = p "This statements is" false 35 | 36 | -------------------------------------------------------------------------------- /6 Simple Algebraic Data Types/6.2 Records.csx: -------------------------------------------------------------------------------- 1 | static class Functions 2 | { 3 | public static string isPrefixOf(string s, string x) => x.StartsWith(s); 4 | //swap argument 5 | public static string isPrefixOf2(string x, string s) => isPrefixOf(s, x); 6 | //This kind of code is hard to reuse outside its context due to the risk of confusion between name and symbol 7 | public static bool startsWithSymbol(string name, string symbol, bool _) => isPrefixOf(symbol, name); 8 | } 9 | 10 | struct Element 11 | { 12 | public string Name { get; } 13 | public string Symbol { get; } 14 | public int AtomicNumber { get; } 15 | 16 | public static Element tupleToElement (string n, string s, int a) => Element(Name = n, Symbol = s, AtomicNumber = a); 17 | public static elemToTuple(Element e) => (e.Name, e.Symbol, e.AtomicNumber); 18 | //Redefine the new function: 19 | //I don't know how to do a human readable one in csharp 20 | public static bool startsWithSymbol2 (Element e) => Functions.isPrefixOf2(e.Symbol, e.Name); 21 | } 22 | 23 | //Personal notes 24 | // link : https://github.com/dotnet/csharplang/blob/master/proposals/records.md 25 | //I will use a struct instead but at the same time I will loose the deconstruct pattern 26 | 27 | -------------------------------------------------------------------------------- /6 Simple Algebraic Data Types/6.2 Records.fsx: -------------------------------------------------------------------------------- 1 | let isPrefixOf s x = (x:string).StartsWith s 2 | 3 | //This kind of code is hard to reuse outside its context due to the risk of confusion between name and symbol 4 | let startsWithSymbol (name, symbol, _) = isPrefixOf symbol name 5 | 6 | //Record Type version 7 | type Element = { Name:string; Symbol:string; AtomicNumber:int } 8 | 9 | let tupleToElement (n, s, a) = { Name = n; Symbol = s; AtomicNumber = a } 10 | let elemToTuple e = e.Name, e.Symbol, e.AtomicNumber 11 | 12 | //We can swap parameter to make it more human readable: 13 | let isPrefixOf' x y = isPrefixOf y x 14 | 15 | //Redefine the new function: 16 | let startsWithSymbol2 e = e.Name |> isPrefixOf' e.Symbol 17 | -------------------------------------------------------------------------------- /6 Simple Algebraic Data Types/6.3 Sum Types.csx: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | //Here is a convenient way to build a Void type 5 | //Sealed is important to stop sum and be sure that this type as no value and is impossible to construct in any way. 6 | sealed class Void 7 | { 8 | private Void() { } 9 | //Try to call this function ! 10 | public static T absurd(Void _) => throw new System.NotImplementedException("you can't call me it is absurd!"); 11 | } 12 | 13 | sealed class Unit 14 | { 15 | private Unit(){ } 16 | 17 | public static Unit Singleton = new Unit(); 18 | } 19 | 20 | //Sum type thanks to Subtyping. It works for this moment.. But lets go deeper in the book :) 21 | 22 | //Personal notes 23 | //Is not an antipattern by having marker interface ?? Lets discuss on a pull request or twitter. 24 | //I don't know but I will use it for this simple case to understand the principle and compliant with the book. 25 | //link : https://blog.ndepend.com/marker-interface-isnt-pattern-good-idea/ 26 | interface Either { } 27 | 28 | struct Left : Either 29 | { 30 | public T Value { get; } 31 | public Left(T v) => Value = v; 32 | } 33 | 34 | //Because there is no Higher kinded types aka generics of generics, I have to write the same code for Right. 35 | //link : https://github.com/dotnet/csharplang/issues/339 36 | //In fsharp sum types already exists so this porcelain and plumbing code not exists in fsharp implementation. 37 | 38 | struct Right : Either 39 | { 40 | public T Value { get; } 41 | public Right(T v) => Value = v; 42 | } 43 | 44 | class Functions 45 | { 46 | //Convenient way to adapt things. Here the compiler ensures that Right is absurd. 47 | //Personal note 48 | //If we have encoded that with NotImplementedException only, the check is at runtime instead at compile time. 49 | //So you may have less problems! 50 | //Here you have a boxing issue : if you would like to use sum type on struct for a O alloc (like Kestrel team plan to do) 51 | public static T simple(Either x) 52 | { 53 | switch (x) 54 | { 55 | case Left l: return Void.absurd(l.Value); 56 | case Right y: return y.Value; 57 | } 58 | throw new NotImplementedException("The compiler can't check if it is total, so it is not really a sum type"); 59 | } 60 | } 61 | 62 | //Here Either is isomorphic to Right because Left is absurd! 63 | var r1 = new Right(Functions.simple(new Right("hello"))); 64 | var r2 = new Right("hello"); 65 | 66 | // /!\ Execute this code line by line due to expression/statement issue 67 | r1.Equals(r2) //true 68 | 69 | //Since we can only build the right part, this type is isomorphic to Right 70 | 71 | /// Personal notes 72 | //Now think about how to implement the StreamReader and StreamWriter. 73 | //One only reads the stream, it is absurd to write. The opposite is true for the writer 74 | //We can build instead a Pipe (reader/writer) and use inheritance polymorphism to build the reader and the writer 75 | 76 | /// Link Pipe in Haskell : https://stackoverflow.com/questions/14131856/whats-the-absurd-function-in-data-void-useful-for 77 | /// For example, Kestrel in dotnet core uses Pipeline pattern for synchronization/timing purpose. 78 | 79 | /// Simple Sum type like enum : 80 | 81 | enum Color 82 | { 83 | Red, 84 | Green, 85 | Blue 86 | } 87 | 88 | // Maybe 89 | //Personal notes : can't be encoded by using enum 90 | interface Maybe 91 | { 92 | } 93 | 94 | sealed class Nothing : Maybe 95 | { 96 | private Nothing () { } 97 | public static Nothing Singleton () => new Nothing(); 98 | } 99 | 100 | struct Just : Maybe 101 | { 102 | public T Value; 103 | public Just(T value) => Value = value; 104 | } 105 | 106 | //Personal notes 107 | //In csharp Nullable is equivalent to Maybe in haskell and Option in fsharp when nullable reference type will be available. 108 | //link : https://blogs.msdn.microsoft.com/dotnet/2017/11/15/nullable-reference-types-in-csharp/ 109 | 110 | //This type could be construct behind Either thanks to our unit type like this : 111 | 112 | // Either /////////////////////////////////////////////////////////////////////// 113 | //Is it possible to reuse our previous definition : 114 | 115 | class Maybe : Either { } //It is a little bit strange.. 116 | 117 | class LeftClass : Either 118 | { 119 | public T Value { get; } 120 | public LeftClass(T v) => Value = v; 121 | } 122 | 123 | class RightClass : Either 124 | { 125 | public T Value { get; } 126 | public RightClass(T v) => Value = v; 127 | } 128 | 129 | class Nothing : LeftClass { public Nothing() : base(Unit.Singleton) { } } //Can't do that with Left struct, so I have to turn the Left as class.. 130 | class Just : RightClass { public Just(T v) : base(v) { } } 131 | 132 | var r1E = new Nothing(); 133 | 134 | var r2E = new Just("hello"); 135 | 136 | var r1E = new Nothing(); 137 | 138 | var r2E = new Just("hello"); 139 | 140 | switch ((Either)r1E) 141 | { 142 | case Nothing _: return "nothing"; 143 | case Just x: return x.Value; 144 | default: throw new System.NotImplementedException("you have added a new type without the matching implementation"); 145 | } 146 | 147 | // Either2 ////////////////////////////////////////////////////////////////////// 148 | // In fact Either interface does not keep track of types : lets create a generic one : 149 | interface Either2 { } 150 | 151 | //Personal notes : this part is not in the book, but I have to translate it in csharp and I don't know what is the approach. Lets discuss it in a pull request 152 | 153 | //It is a little bit strange, now in the definition of Left we have to keep the Right type ?? 154 | struct Left2 : Either2 155 | { 156 | public L Value { get; } 157 | public Left2(L x) => Value = x; 158 | } 159 | 160 | //Duplicating stuff 161 | struct Right2 : Either2 162 | { 163 | public R Value { get; } 164 | public Right2(R x) => Value = x; 165 | } 166 | 167 | //Now it is okay but we have introduced a mutual dependency in the type definition between Left and Right type ?! 168 | class Maybe2 : Either2 { } 169 | 170 | var r1E2 = new Right2("hello"); 171 | var r2E2 = new Left2(Unit.Singleton); 172 | 173 | switch ((Either2)r1E2) 174 | { 175 | case Left2 _: return "nothing"; 176 | case Right2 x: return x.Value; 177 | default: throw new System.NotImplementedException("you have added a new type without the matching implementation"); 178 | } 179 | 180 | // Either3 ////////////////////////////////////////////////////////////////////// 181 | //We can define the Either with only one generic type because. It is a sum after all.. But ... 182 | interface Either3 { } 183 | 184 | struct Left3 : Either3 185 | { 186 | public L Value { get; } 187 | public Left3(L x) => Value = x; 188 | } 189 | 190 | //Duplicating stuff 191 | struct Right3 : Either3 192 | { 193 | public R Value { get; } 194 | public Right3(R x) => Value = x; 195 | } 196 | 197 | class Maybe3 : Either3 { } 198 | 199 | var r1E3 = new Right3("hello"); 200 | var r2E3 = new Left3(Unit.Singleton); 201 | 202 | //This code does not compile at all because Unit != string 203 | switch ((Either3)r1E3) 204 | { 205 | case Left3 _: return "nothing"; 206 | case Right3 x: return x.Value; 207 | default: throw new System.NotImplementedException("you have added a new type without the matching implementation"); 208 | } 209 | 210 | // Check the factorizers properties.. 211 | static class Either3Extension 212 | { 213 | //How could we write the factorizers. 214 | //We could do it but it is not a sum anymore.. The type of left and right should be the same.. 215 | //If this sample is a little bit hard, try to implement the prodToSum and sumToProd of the chapter 6.4 216 | public static Either3 factorizers(Either3 x, Func f, Func g) 217 | { 218 | switch (x) 219 | { 220 | case Left3 l: return new Left3(f(l.Value)); 221 | case Right3 r: return new Right3(g(r.Value)); 222 | } 223 | throw new System.NotImplementedException("unreachable"); 224 | } 225 | } 226 | 227 | 228 | 229 | 230 | 231 | 232 | // Either4 ////////////////////////////////////////////////////////////////////// 233 | // Link : https://mikhail.io/2016/01/validation-with-either-data-type-in-csharp/ 234 | // Link: https://davesquared.net/2014/04/either.html 235 | public class Either4 236 | { 237 | private readonly TL left; 238 | private readonly TR right; 239 | private readonly bool isLeft; 240 | 241 | public Either4(TL left) 242 | { 243 | this.left = left; 244 | this.isLeft = true; 245 | } 246 | 247 | public Either4(TR right) 248 | { 249 | this.right = right; 250 | this.isLeft = false; 251 | } 252 | 253 | public TL Left => left; 254 | 255 | public TR Right => right; 256 | 257 | public T Match4(Func leftFunc, Func rightFunc) 258 | => this.isLeft ? leftFunc(this.left) : rightFunc(this.right); 259 | 260 | public override bool Equals(object obj) 261 | { 262 | var item = obj as Either4; 263 | if (item == null) 264 | { 265 | return false; 266 | } 267 | return item.Match4( 268 | left1 => this.Match4(left2 => left2.Equals(left1), right2 => false), 269 | right1 => this.Match4(left2 => false, right2 => right2.Equals(right1)) 270 | ); 271 | } 272 | } 273 | 274 | //Here Either is isomorphic to Right because Left is absurd! 275 | var r14 = new Either4( 276 | new Either4("hello") 277 | .Match4( 278 | left => left, right => throw new Exception("error!")) 279 | ); 280 | var r24 = new Either4("hello"); 281 | 282 | // /!\ Execute this code line by line due to expression/statement issue 283 | r14.Equals(r24) //true 284 | 285 | 286 | class Just4 : Either4 { public Just4(T v) : base(v) { } } 287 | class Nothing4 : Either4 { public Nothing4() : base(Unit.Singleton) { } } 288 | 289 | var r1E4 = new Just4("hello"); 290 | var r2E4 = new Nothing4(); 291 | 292 | switch ((Either4)r1E4) 293 | { 294 | case Nothing4 _: return "nothing"; 295 | case Just4 x: return x.Right; 296 | default: throw new System.NotImplementedException("you have added a new type without the matching implementation"); 297 | } 298 | 299 | 300 | // Either5 ////////////////////////////////////////////////////////////////////// 301 | // Either implemented by using the Vistor pattern 302 | public interface IEitherVisitor 303 | { 304 | A visitLeft(Left5 v); 305 | B visitRight(Right5 v); 306 | }; 307 | 308 | public interface IEither5 309 | { 310 | A acceptLeft(IEitherVisitor v); 311 | B acceptRight(IEitherVisitor v); 312 | }; 313 | 314 | public struct Left5 : IEither5 315 | { 316 | public A Value { get; } 317 | public Left5(A v) => Value = v; 318 | public A acceptLeft(IEitherVisitor v) => Value; 319 | public B acceptRight(IEitherVisitor v) => throw new Exception("only Left"); 320 | }; 321 | 322 | public struct Right5 : IEither5 323 | { 324 | public B Value { get; } 325 | public Right5(B v) => Value = v; 326 | public A acceptLeft(IEitherVisitor v) => throw new Exception("only right"); 327 | public B acceptRight(IEitherVisitor v) => Value; 328 | }; 329 | 330 | public class EitherVisitor : IEitherVisitor 331 | { 332 | public A visitLeft(Left5 v) => v.acceptLeft(this); 333 | public B visitRight(Right5 v) => v.acceptRight(this); 334 | }; 335 | 336 | var visitor = new EitherVisitor(); 337 | var r1 = new Right5(visitor.visitRight(new Right5("hello"))); 338 | var r2 = new Right5("hello"); 339 | 340 | r1.Equals(r2) // true 341 | 342 | 343 | 344 | 345 | 346 | //Wrap Up 347 | //Here, only the Either2, 4 and 5 are valid with properties and is compliant. 348 | //For the Either2, the client can use the new csharp syntax for pattern matching switch statement but the type definition of left and right are mutually dependent.. 349 | 350 | //////////////////////////////// 351 | 352 | // list : 353 | // yield is the keyword equivalent of Cons 354 | // Enumerable.Empty is the equivalent of Nil + Linq conversion to the type List 355 | // The most type used for list in c# is List 356 | 357 | var empty = Enumerable.Empty().ToList(); 358 | 359 | var l1 = empty.ToList(); 360 | //The add method mute the list. So the last element will be different depending where you add items.. 361 | l1.Add(1); 362 | var x = l1.Last(); 363 | 364 | l1.Add(2); 365 | var y = l1.Last(); 366 | 367 | x == y //false! 368 | 369 | //link Give a try at ImmutableList : https://msdn.microsoft.com/en-us/library/dn467185(v=vs.111).aspx 370 | 371 | empty.FirstOrDefault() //0 ?? what?? Implicit zero on default constructor provided by struct. 372 | 373 | var l3 = new[] { 0 }; 374 | 375 | empty.FirstOrDefault() == l3.FirstOrDefault() //true, now we are not able to see if it is the first element or not ?0? 376 | 377 | //now it is better but the compiler does not help us because there is no Nullable for FirstOrDefault (due to the reference type issue in Nullable) 378 | 379 | //A better one for value types 380 | empty.Select(x => new Nullable(x)).FirstOrDefault() // It outputs null but it is a true Nullable without value.. 381 | 382 | -------------------------------------------------------------------------------- /6 Simple Algebraic Data Types/6.3 Sum Types.fsx: -------------------------------------------------------------------------------- 1 | //Here is a convenient way to build a Void type 2 | //Sealed is important to stop sum and be sure that this type as no value and is impossible to construct in any way. 3 | type [] Void = private new () = { } 4 | 5 | //Try to call this function ! 6 | let absurd (_:Void) = failwith "you can't call me it is absurd!" 7 | 8 | type Either<'a, 'b> = Left of 'a | Right of 'b 9 | 10 | //Convenient way to adapt things. Here the compiler ensures that Right is absurd. 11 | //If we have encoded that with NotImplementedException only, the check is at runtime instead at compile time. 12 | //So you may have less problems! 13 | let simple = function 14 | | Left x -> absurd x 15 | | Right y -> y 16 | 17 | //Here Either is isomorphic to Right because Left is absurd! 18 | Right "hello" |> simple |> Right = Right "hello" 19 | //Since we can only build the right part, this type is isomorphic to Right 20 | 21 | /// Personal notes 22 | //Now think about how to implement the StreamReader and StreamWriter. 23 | //One only reads the stream, it is absurd to write. The opposite is true for the writer 24 | //We can build a Pipe instead (reader/writer, producer/consumer) and use inheritance polymorphism to build the reader and the writer 25 | 26 | /// Link Pipe in Haskell : https://stackoverflow.com/questions/14131856/whats-the-absurd-function-in-data-void-useful-for 27 | /// For example, Kestrel in dotnet core uses Pipeline pattern for synchronization/timing purpose. 28 | 29 | /// Simple Sum type like enum : 30 | type Color = Red | Green | Blue 31 | 32 | // Maybe 33 | type Maybe<'a> = Nothing | Just of 'a 34 | //This type could be construct behind Either thanks to our unit type like this : 35 | type MaybeE<'a> = Either 36 | 37 | //In fsharp, Option is the equivalent of the Maybe one. None = Nothing and Just = Some. 38 | 39 | // list : 40 | // (::) is the equivalent of Cons 41 | // [] is the equivalent of Nil 42 | 1 :: [] = [1] //true 43 | 1 :: 2 :: [] = [1;2] //true 44 | 45 | //equivalent to maybeHead 46 | [] |> List.tryHead = None 47 | [1] |> List.tryHead = Some 1 48 | 49 | //Same things for last : equivalent to maybeTail 50 | [] |> List.tryLast = None 51 | [1] |> List.tryLast = Some 1 -------------------------------------------------------------------------------- /6 Simple Algebraic Data Types/6.4 Algebra Types.csx: -------------------------------------------------------------------------------- 1 | interface Either { } 2 | 3 | struct Left : Either 4 | { 5 | public L Value { get; } 6 | public Left(L x) => Value = x; 7 | } 8 | 9 | struct Right : Either 10 | { 11 | public R Value { get; } 12 | public Right(R x) => Value = x; 13 | } 14 | 15 | //distribution : 16 | 17 | static class Functions 18 | { 19 | public static Either, ValueTuple> prodToSum(ValueTuple> t) 20 | { 21 | var (x, e) = t; 22 | 23 | switch (e) 24 | { 25 | case Left l: return new Left, ValueTuple>((x, l.Value)); 26 | case Right r: return new Right, ValueTuple>((x, r.Value)); 27 | } 28 | throw new System.NotImplementedException("not reachable"); 29 | } 30 | 31 | public static ValueTuple> sumToProd(Either, ValueTuple> e) 32 | { 33 | switch (e) 34 | { 35 | //This is why subtyping as sum type is not trivial 36 | case Left, ValueTuple> l: 37 | var (x, v) = l.Value; 38 | return (x, new Left(v)); 39 | case Right, ValueTuple> l: 40 | var (x, v) = l.Value; 41 | return (x, new Right(v)); 42 | } 43 | throw new System.NotImplementedException("not reachable"); 44 | } 45 | } 46 | 47 | var x = ("hello", new Left("world")); 48 | 49 | //semiring or rig : No type substraction is provided 50 | Functions.sumToProd((Functions.prodToSum(x))).Equals(x) //true -------------------------------------------------------------------------------- /6 Simple Algebraic Data Types/6.4 Algebra Types.fsx: -------------------------------------------------------------------------------- 1 | type Either<'a, 'b> = Left of 'a | Right of 'b 2 | 3 | //distribution : 4 | 5 | let prodToSum (a, b) = 6 | match b with 7 | | Left b' -> Left (a, b') 8 | | Right b' -> Right (a, b') 9 | 10 | let sumToProd = function 11 | | Left (a, b) -> a, Left b 12 | | Right (a, b) -> a, Right b 13 | 14 | 15 | let x = ("hello", Left "world") 16 | 17 | //semiring or rig : No type substraction is provided 18 | (prodToSum >> sumToProd) x = x //true -------------------------------------------------------------------------------- /6 Simple Algebraic Data Types/README.md: -------------------------------------------------------------------------------- 1 | # Simple Algebraic Data Types 2 | 3 | This chapter explains well what is a Sum and Product types and how we could use them together. 4 | 5 | ## Personal notes 6 | In csharp we often use Subtyping as Sum type. There is 2 things in Sum types : the total one, where the compiler checks that 7 | each case is treated (Discriminated union in fsharp, can't do that in csharp for now) and the open one (partial) with Subtype. 8 | 9 | Sum type as Subtyping creates strange mutually type dependencies in the Either implementation : Pull request accepted! 10 | 11 | Because csharp does not support sumtype, I would like to summurize solutions because there is no total equivalent : 12 | 13 | Here is all implementation 14 | 15 | - [Pattern matching](https://docs.microsoft.com/en-us/dotnet/csharp/pattern-matching) : you can deconstruct what you have construct. 16 | For either you can unwrap the left case and get the value inside. The pattern matching feature with sum type in functional programming is really helpful because you can combine case deeper and deeper with less cyclomatic complexity in your code. 17 | 18 | You can encode your pattern matching thanks to if statements but you may have a higher cyclomatic complexity and have to split your method. 19 | 20 | - Boxing : When you have to use a struct that implement an interface, every time you use the struct as interface, you have a boxing issue. For 0 alloc pattern it could be an issue. 21 | 22 | - Marker interface : https://blog.ndepend.com/marker-interface-isnt-pattern-good-idea/. Is it an antipattern ? 23 | 24 | - Polymorphism : implementation without the left and right type in the Either type definition can help you to implement it faster but you can't use polymorphism and reuse your code explicitly' 25 | 26 | Note that if we use inheritance and use the pattern matching switch expression we have to use an interface. 27 | If we have to inherit from case, we could not use struct at root. So inheritance at case level is not possible for struct. 28 | 29 | - Either : inheritance at case level. Can't use struct but the pattern matching syntax works with upper cast. 30 | 31 | - Either2 : try to make inheritance at case level with struct support. 32 | But for each new case you have to add the new type on all types. By doing this you may have some regretion. 33 | Cases are mutually dependent. 34 | - Either3 : try to keep one type to avoid case dependencies. But now we can't match case anymore.. It is not a valid solution at all. 35 | We can define the type Maybe ```(class Maybe2 : Either2 { })``` only for info.. (can't convert Maybe2 -> Right directly). 36 | - Either4 : this [implementation](https://mikhail.io/2016/01/validation-with-either-data-type-in-csharp/) is inspired by [@MikhailShilkov](https://twitter.com/MikhailShilkov). 37 | Now the left and right type are inside. Now the type definition is better at a first glance and same as fsharp but 38 | we have now 2 ways to get the value and only one is valid (Left and Right property). 39 | - Either5: this implementation try to solve the struct issue of the Either4 but we cannot we the csharp pattern matching syntax anymore. 40 | 41 | | Ranking | Name | Pattern matching | Boxing | Marker interface | Polymorphism | Support struct | 42 | |---------|---------------|------------------|--------|------------------|--------------------|----------------| 43 | | #4 | Either | Switch keyword | - | Yes | At case level | No | 44 | | #2 | Either2 | Switch keyword | Yes | Yes | At case level | Yes | 45 | | - | Either3 | Not Possible | Yes | Yes | At case level | Yes | 46 | | #1 | Either4 | No. With method | - | No | At type level | No | 47 | | #3 | Either5 | No. | Yes | Yes | At case level | Yes | 48 | 49 | To summarize, there is no total solution for csharp sum type. You have to choose one dependending your case but we cannot build a lib that supply only one valid implementation for either and compose maybe or nullable over it. 50 | Having sum types can increase composability of types. 51 | 52 | I will use the Either2 which is not the best but is compliant with the pattern matching feature that is necessary to implement function over our sum type. 53 | 54 | Thanks to [giuliohome](https://twitter.com/giuliohome_2017) who help me to add more implementations. -------------------------------------------------------------------------------- /7 Functor/7.1.1 The Maybe Functor.csx: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | interface Maybe { } 4 | 5 | struct Nothing : Maybe { } 6 | 7 | struct Just : Maybe 8 | { 9 | public T Value { get; } 10 | public Just(T v) => Value = v; 11 | } 12 | 13 | static class Maybe 14 | { 15 | public static Maybe fmap(Func f, Maybe x) 16 | { 17 | switch (x) 18 | { 19 | case Nothing _: return new Nothing(); 20 | case Just j: return new Just(f(j.Value)); 21 | } 22 | throw new NotImplementedException("absurd, there is no other cases but we don't have he garantee"); 23 | } 24 | } 25 | 26 | Maybe.fmap(x => x, new Nothing()).Equals(new Nothing()) //true 27 | 28 | //Nullable is the new equivalent of Maybe (Nullable will be available for reference type in csharp soon) 29 | //Link : https://blogs.msdn.microsoft.com/dotnet/2017/11/15/nullable-reference-types-in-csharp/ 30 | 31 | //In csharp there is an operator to traverse structure non null value or passing as parameter inside a function : 32 | 33 | static class NullableExtension 34 | { 35 | public static Nullable fmap(Func f, Nullable x) 36 | where T : struct 37 | where R : struct 38 | { 39 | if (x.HasValue) return new Nullable(f(x.Value)); 40 | return new Nullable(); 41 | } 42 | } 43 | 44 | // Nullable is not completely useful because it is compatible with struct only. 45 | // As you may notice the default constructor occurs on the GetValueOrDefault with implicit default constructor as zero causing less compatibility with reference types. 46 | 47 | // 48 | new Nullable ().Equals(NullableExtension.fmap (x => x, new Nullable())) //true 49 | 50 | // The maybe one is compatible with struct and reference type. -------------------------------------------------------------------------------- /7 Functor/7.1.1 The Maybe Functor.fsx: -------------------------------------------------------------------------------- 1 | type [] Maybe<'a> = Nothing | Just of 'a 2 | 3 | //Here is the fmap implementation with types 4 | let fmap (f:'a->'b) (x:Maybe<'a>) : Maybe<'b> = 5 | match x with 6 | | Nothing -> Nothing 7 | | Just x' -> Just(f x') 8 | 9 | fmap id Nothing = Nothing //true 10 | 11 | //Now declare the fmap in a module with lighter syntax : 12 | module Maybe = 13 | let fmap f = function Nothing -> Nothing | Just x -> Just (f x) 14 | 15 | Maybe.fmap id Nothing = Nothing //true 16 | 17 | //In fsharp, Option already exists and act as Maybe 18 | Option.map id None = None 19 | Option.map id (Some 1) = Some 1 -------------------------------------------------------------------------------- /7 Functor/7.1.6 The List Functor.csx: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | interface List { } 4 | 5 | class Nil : List { } 6 | 7 | class Cons : List 8 | { 9 | public T Head { get; } 10 | public List Tail { get; } 11 | 12 | public Cons(T head, List tail) 13 | { 14 | Head = head; 15 | Tail = tail; 16 | } 17 | } 18 | 19 | static class List 20 | { 21 | public static List map(Func f, List l) 22 | { 23 | switch (l) 24 | { 25 | case Nil _: return new Nil(); 26 | case Cons c: return new Cons(f(c.Head), map(f, c.Tail)); 27 | } 28 | throw new NotImplementedException("absurd"); 29 | } 30 | 31 | public static List init(Func f, int n) 32 | { 33 | List l = new Nil(); 34 | for (var i = n; i > 0; i--) 35 | { 36 | //Here l is mutating.. 37 | l = new Cons(f(i), l); 38 | } 39 | return l; 40 | } 41 | 42 | public static List initRec(Func f, int n) 43 | { 44 | List nestedInitRec(int m, List l) 45 | { 46 | if (m > 0) 47 | { 48 | return nestedInitRec(m - 1, new Cons(f(m), l)); 49 | } 50 | else return l; 51 | } 52 | 53 | return nestedInitRec(n, new Nil()); 54 | } 55 | } 56 | 57 | static class TailRecursiveList 58 | { 59 | // This code works with tailcall optimization only in x64 debug and release for dotnetcore but only release for dotnet framework. 60 | // We are converting the Monoid to another one (Nil as seed/mempty and folder as Cons+folder/mappend) 61 | public static R fold(Func folder, R seed, List x) 62 | { 63 | switch (x) 64 | { 65 | case Nil _: return seed; 66 | case Cons c: return fold(folder, folder(seed, c.Head), c.Tail); 67 | } 68 | throw new NotImplementedException("absurd"); 69 | } 70 | 71 | //A reverse function because every time we use fold, by rebuilding from zero, in the end, the order is reversed. 72 | public static List rev(List l) => fold((t, x) => new Cons(x, t), (List)new Nil(), l); 73 | 74 | //Here we are rebuilding the list from Nil by applying every x to f. Note that the order is reversed 75 | //write map through fold + rev to have tail-recursive optimization 76 | //Here we are using the List Monoid to map function (Nil as mempty and Cons as mappend) through fold 77 | //Here we are on O(n^2) (fold + rev) but actual Fsharp List implementation is better than this one. 78 | public static List map(Func f, List l) => rev(fold((t, x) => new Cons(f(x), t), (List)new Nil(), l)); 79 | } 80 | 81 | //This impl does not works in csharp interactive but works on release x64 configuration. 82 | //I don't know how to configure CshapInteractive ?! 83 | var of = List.initRec(y => y, 100000000); 84 | 85 | var x = List.init(y => y, 100000000); 86 | var x2 = List.map(y => y, x); //StackOverflow! 87 | 88 | //Here we have a stack overflow! 89 | var x3 = TailRecursiveList.map(y => y, x); 90 | 91 | //Lets try with a little set.. 92 | 93 | var x4 = List.initRec(y => y, 100); 94 | var x5 = List.map(x => x, x4); 95 | 96 | -------------------------------------------------------------------------------- /7 Functor/7.1.6 The List Functor.fsx: -------------------------------------------------------------------------------- 1 | 2 | type List<'a> = Nil | Cons of 'a * List<'a> 3 | 4 | //Here all comments are personal notes to have a list functor with tail-recursive optimization. 5 | 6 | //Aka Instance of Functor (pattern used in fsharp implementation) : Build a module and define function and implementation thanks to the typeclassopedia (Haskell wiki) 7 | module List = 8 | //we have to use the rec keyword. This version is not tail-recursive (in the end we will have a stack overflow). Through fold it is possible 9 | //Here we have to wait the tail result before returning 10 | let rec map f = function Nil -> Nil | Cons (x, t) -> Cons (f x, map f t) 11 | 12 | let init f n = 13 | let rec initRec f n l = 14 | if n > 0 then initRec f (n - 1) (Cons(f n, l)) 15 | else l 16 | initRec f n Nil 17 | 18 | //Personal Notes 19 | module TailRecursiveList = 20 | //We have to use the rec keyword but here we are rebuilding the list one by one and it the tail-recursive optimization works. 21 | //(seed acts as accumulator) 22 | // We are converting the Monoid to another one (Nil as seed/mempty and folder as Cons+folder/mappend) 23 | let rec fold folder seed = function Nil -> seed | Cons (h, t) -> fold folder (folder seed h) t 24 | 25 | //A reverse function because every time we use fold, by rebuilding from zero, in the end, the order is reversed. 26 | let rev l = fold (fun seed x -> Cons (x, seed)) Nil l 27 | 28 | //Here we are rebuilding the list from Nil by applying every x to f. Note that the order is reversed 29 | //write map through fold + rev to have tail-recursive optimization 30 | //Here we are using the List Monoid to map function (Nil as mempty and Cons as mappend) through fold 31 | //Here we are on O(n^2) (fold + rev) but actual Fsharp List implementation is better than this one. 32 | let map f = fold (fun t x -> Cons (f x, t)) Nil >> rev 33 | 34 | let x = List.init id 100000000 35 | 36 | let x' = x |> List.map id //Stack overflow! 37 | 38 | let x'' = x |> TailRecursiveList.map id //No stack overflow anymore (with --optimize option or --tailcall)! 39 | 40 | x'' = x //answer is long but true! 41 | 42 | //In fsharp list already exists! 43 | 44 | let y = FSharp.Collections.List.init 100000000 id 45 | let y' = y |> FSharp.Collections.List.map id //No stack overflow, no reversed order problem :) 46 | y = y' //true -------------------------------------------------------------------------------- /7 Functor/7.1.7 The Reader Functor.csx: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | //Can't do that since Func is sealed. So lets use Func directly.. 4 | //class Functor : Func { } 5 | 6 | static class Functor 7 | { 8 | public static Func map(Func f, Func g) => (x) => f(g(x)); 9 | } 10 | 11 | var rf = (Functor.map(x => x, y => y)); 12 | rf(1) == 1 //true 13 | -------------------------------------------------------------------------------- /7 Functor/7.1.7 The Reader Functor.fsx: -------------------------------------------------------------------------------- 1 | type Functor<'r, 'a> = ('r -> 'a) 2 | 3 | module Functor = 4 | let map (f:'a -> 'b) (g:Functor<'r, 'a>) : Functor<'r, 'b> = f << g 5 | 6 | module Functor2 = 7 | let map = (<<) 8 | 9 | Functor.map id id 1 = Functor2.map id id 1 // We have -------------------------------------------------------------------------------- /7 Functor/7.2 Functors as Containers.csx: -------------------------------------------------------------------------------- 1 | //Infinite List. 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | static class EnumerableExtension 7 | { 8 | public static IEnumerable InitInfinite(Func f) 9 | { 10 | int counter = 0; 11 | while (true) 12 | { 13 | yield return f(counter); 14 | counter++; 15 | } 16 | } 17 | } 18 | 19 | var x = EnumerableExtension.InitInfinite(x=>x).Take(2).ToList(); 20 | 21 | x.SequenceEqual(new List { 0, 1 }); //true 22 | 23 | class Const 24 | { 25 | public C Value { get; } 26 | public Const(C v) => Value = v; 27 | } 28 | class Const : Const 29 | { 30 | public Const(C v) : base(v) { } 31 | } 32 | 33 | static class Const 34 | { 35 | //"fmap is free to ignore its function upon" 36 | static Const fmap(Func _, Const x) => new Const(x.Value); 37 | } 38 | 39 | -------------------------------------------------------------------------------- /7 Functor/7.2 Functors as Containers.fsx: -------------------------------------------------------------------------------- 1 | // Infinite seq 2 | 3 | let x = Seq.initInfinite id 4 | 5 | x |> Seq.take 2 |> Seq.toList = [ 0; 1 ] 6 | 7 | // Const : demo where value inside the functor is not important. 8 | 9 | type Const<'c, 'a> = Const of 'c 10 | 11 | module Const = 12 | //"fmap is free to ignore its function upon" 13 | let fmap ((_:'a -> 'b), ((Const v):Const<'c, 'a>)) : Const<'c, 'b> = Const (v) -------------------------------------------------------------------------------- /7 Functor/7.3 Functor Composition.csx: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | interface Maybe { } 6 | class Nothing : Maybe 7 | { 8 | public Nothing() { } 9 | } 10 | 11 | class Just : Maybe 12 | { 13 | public T Value { get; } 14 | public Just(T v) => Value = v; 15 | } 16 | 17 | static class Maybe 18 | { 19 | public static Maybe map(Func f, Maybe x) 20 | { 21 | switch (x) 22 | { 23 | case Nothing _: return (Maybe)new Nothing(); 24 | case Just j: return new Just(f(j.Value)); 25 | } 26 | throw new NotImplementedException("absurd"); 27 | } 28 | } 29 | 30 | static class ListExtension 31 | { 32 | public static Maybe> tryTail(List l) 33 | { 34 | if (l.Any()) return new Just>(l.Skip(1).ToList()); 35 | else return new Nothing>(); 36 | } 37 | 38 | //As you may notice Select is the equivalent of map. 39 | //The keyword is inspired by the SQL syntax but behind it is a monad 40 | public static List map(Func f, List x) => x.Select(f).ToList(); 41 | } 42 | 43 | Maybe> nothing = ListExtension.tryTail(new List()); 44 | var just = ListExtension.tryTail(new List { 1, 2 }); 45 | 46 | Maybe.map(x => ListExtension.map(y => $"my value is {y}", x), just); //"my value is 2" 47 | Maybe.map(x => ListExtension.map(y => $"my value is {y}", x), nothing); //Nothing 48 | 49 | -------------------------------------------------------------------------------- /7 Functor/7.3 Functor Composition.fsx: -------------------------------------------------------------------------------- 1 | //Maybe + List 2 | 3 | type Maybe<'a> = Nothing | Just of 'a 4 | 5 | module Maybe = 6 | let map f = function Nothing -> Nothing | Just x -> Just (f x) 7 | 8 | module List = 9 | let maybeTail = function [] -> Nothing | _::tail -> Just tail 10 | let tryTail = function [] -> None | _::tail -> Some tail 11 | 12 | 13 | [] |> List.maybeTail |> Maybe.map (List.map (sprintf "my value is %i")) //Nothing 14 | 15 | [1] |> List.maybeTail |> Maybe.map (List.map (sprintf "my value is %i")) //Just [] 16 | [1;2] |> List.maybeTail |> Maybe.map (List.map (sprintf "my value is %i")) //Just ["my value is 2"] 17 | 18 | //In fsharp you can use Option in place of Maybe like this : 19 | 20 | [1] |> List.tryTail |> Option.map (List.map (sprintf "my value is %i")) //Some [] 21 | [1;2] |> List.tryTail |> Option.map (List.map (sprintf "my value is %i")) //Some ["my value is 2"] -------------------------------------------------------------------------------- /7 Functor/README.md: -------------------------------------------------------------------------------- 1 | # 7 Functor 2 | 3 | ## 7.1.1 The Maybe Functor 4 | 5 | ### Personal Notes 6 | In .Net there is the nullable equivalent for value type. For reference type we have to wait https://github.com/dotnet/csharplang/wiki/Nullable-Reference-Types-Preview. 7 | But you can still build your own with FSharp.Core option type or a Maybe one. 8 | 9 | By using it you will see that the custom ```?``` operator is useless when you define fmap and so on.. But it is an another story.. 10 | 11 | In Fsharp, Option type is the equivalent of the Maybe type. 12 | 13 | ## 7.1.2 Equational Reasoning 14 | 15 | Read the chapter. To summarize, equality is important when you want to prove thing except when your function uses side effect. 16 | 17 | Those equalities have been use in 7.1.1 scripts. 18 | 19 | ## 7.1.3 Optional 20 | 21 | As always read it. The author explains very well what we try to implement in 7.1.1 for csx script. 22 | 23 | ## 7.1.4 Typeclasses (Mostly Personal notes) 24 | ```Haskell 25 | class Functor f where 26 | fmap :: (a -> b) -> f a -> f b 27 | ``` 28 | 29 | Type classes does not exists in .Net. 30 | The C++ equivalent is template-template whereas there is no generic-generic or generic of generic. 31 | This is a limitation of .Net. 32 | 33 | If you want to see an equivalent, you should read the chapter 7.1.5 Functor in C++. 34 | 35 | Even if type classes were available in .Net, we could not implement Functor as is. 36 | We need a feature called type constructor which is generic-generic dependent. 37 | In the given definition of functor ```f a``` means ```f``` where ```f``` and ```a``` are generics. 38 | 39 | To implement properly Functor type classes we need 3 things in order : 40 | 41 | 1/ [Types classes or Trait](https://github.com/fsharp/fslang-suggestions/issues/243) 42 | 43 | 2/ [Generic of Generic](https://github.com/dotnet/csharplang/issues/339) 44 | 45 | 3/ [Type constructor](https://github.com/fsharp/fslang-suggestions/issues/243#issuecomment-260186368) 46 | 47 | [FStan](https://github.com/thautwarm/FSTan/blob/master/README.md) is an excellent alternative. 48 | You can still use abtract class and interface and made static things by defining some functions in a prelude class/module and you have type classes after all. 49 | But you may consider that this abstraction has a cost at runtime. 50 | 51 | ### Deal with it! 52 | In .Net you don't have type classes stricly checked by the compiler but you can use implicitly. 53 | To do it right, you should check the Haskell wiki : https://wiki.haskell.org/File:Typeclassopedia-diagram.png 54 | 55 | For example : it is easy to traverse structure through fold because a foldable monoid is traversable. 56 | You can fold the monoid (mempty and mappend) first and transform it to another type like the aggregate function does with list. 57 | 58 | If you build types by following the typeclassopedia rules, you have the benefits of the type composition property. 59 | 60 | The aim of the book is to understand Category theory through a programming lang. 61 | I guess it is not very important to have type classes. 62 | We can just continue with csharp and fsharp by following the rules. 63 | 64 | In fsharp modules there is no type classes but fmap (map) is available on mostly all modules. 65 | You can follow the same rules in your domain as substitution of type classes as an informal way. 66 | 67 | ### No Silver Bullet (Very personal but not offensive notes :)) 68 | 69 | I often see questions like : What is the best FP lang ? And there is a lots of answer (Scala is better, Fsharp, Haskell, Rust and so on..). 70 | I think it is a very personal choice dependending or OUR context and it works fine. 71 | But if you think that your FP lang is the best, did you try to use it in a completely different context ? 72 | 73 | Let's check that we have [No Silver Bullet](https://en.wikipedia.org/wiki/No_Silver_Bullet) 74 | - Haskell is pure and well constructed thanks to functional pattern by design. 75 | - FSharp is hybrid and bring functional first language to .Net ecosystem which Haskell can't (but some project try to do it : https://wiki.haskell.org/Common_Language_Runtime).. 76 | - Scala is the same as FSharp to Java except that they bring types classes and type constructor but it is [not perfect](https://github.com/lampepfl/dotty/issues/2047) due to the OOP model. [Sparkle](https://github.com/tweag/sparkle) uses jvm to use spark infrastructure with Haskell. 77 | - Except FSharp, none of this functional programming offers type providers. I use/abuse it in my daily coding basis because I have to implement at least 200 apis (I work for a software editor with hundreds of partnerships). Even if you can use templates in Haskell, you may endup with some compiler limits. 78 | - And there is a lots of langs : https://en.wikipedia.org/wiki/Functional_programming#Coding_styles 79 | 80 | So dependending of your context you may have to choose 1 or n FP lang and interop thanks to microservices to have the full power of functional programming. 81 | I guess that context is very rare and honestly one per ecosystem (legacy) is ok. 82 | 83 | There is [Idris](https://www.idris-lang.org/) (maybe a future bronze bullet :)) (based on the Haskell ecosystem) which try to bring fsharp [type provider with dependent typing](http://www.davidchristiansen.dk/pubs/dependent-type-providers.pdf). 84 | 85 | If you want to go deeper with dependent typing after Category Theory for Programmers you should consider reading [The little typer](https://mitpress.mit.edu/books/little-typer), it blows my mind! 86 | 87 | Read the [Idris paper](http://www.davidchristiansen.dk/pubs/dependent-type-providers.pdf) 2.1 chapter which gives a definition of type provider better than fsharp! 88 | 89 | To see a concrete of the Deal With it pattern, jump to 7.1.6 List Functor. 90 | 91 | ## 7.1.6 List Functor (Personal notes). 92 | 93 | Type classes does not exists in .Net and we have to deal with it and follow the rule of fsharp (to code like if we have type classes virtually/in mind). 94 | 95 | In fsharp list implementation, there is associated function definition of a type inside a module (ie: List module for 'a List). 96 | The module follows the rules of the functor by defining a function map but the compiler can't check that for us. 97 | We can opt for fsi file to check but it is manual and we have to copy the functor def on all modules. 98 | 99 | The structure of the type list is recursive. In .Net we have some issue when the method/function is not tail recursive, you may endup with a StackOverflow. 100 | To avoid this, we defined the method that traverses the list structure in a tail recursive way (fold). 101 | 102 | ## 7.1.7 The Reader Functor 103 | In fsharp we could define the reader functor by using the ```<<``` operator. 104 | 105 | ## 7.2 Functors as Containers 106 | Read the chapter to have the full story. 107 | Where .Net implements infinite with a state machine (implemented with goto and mutation). 108 | Haskell use a function (with closure) and eval it when we need the value (this is why Haskell is lazy). 109 | Haskell is lazy by default, you can build an infinite list when a fsharp list is finite. 110 | Seq, alias of .net IEnumerable uses lazy with yield keyword / seq computation expression. 111 | Like Haskell you can't compute the length of an infinite list of values. 112 | 113 | ## 7.3 Functor Composition 114 | How to traverse Functor^2 ? By traversing it with map twice (one for maybe and one for list)! 115 | -------------------------------------------------------------------------------- /8 Functoriality/8.1 Bifunctors.csx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cboudereau/category-theory-for-dotnet-programmers/fff19d76890a564a968f8c54bf6a4c5ea9fc3d9c/8 Functoriality/8.1 Bifunctors.csx -------------------------------------------------------------------------------- /8 Functoriality/8.1 Bifunctors.fsx: -------------------------------------------------------------------------------- 1 | type BiFunctor<'a, 'b> = BiFunctor of ('a * 'b) 2 | 3 | module BiFunctor = 4 | let bimap g h (BiFunctor x) = (x |> fst |> g, x |> snd |> h) |> BiFunctor 5 | let first g = bimap g id 6 | let second h = bimap id h 7 | 8 | let x = BiFunctor (1,2) 9 | 10 | BiFunctor.bimap id id x = x //true 11 | BiFunctor.first id x = x //true 12 | BiFunctor.second id x = x //true 13 | 14 | BiFunctor.bimap string string x = BiFunctor ("1", "2") //true 15 | BiFunctor.first string x = BiFunctor ("1", 2) //true 16 | BiFunctor.second string x = BiFunctor (1, "2") //true 17 | -------------------------------------------------------------------------------- /8 Functoriality/8.2 Product and Coproduct Bifunctors.fsx: -------------------------------------------------------------------------------- 1 | type Either<'a, 'b> = Left of 'a | Right of 'b 2 | 3 | module Either = 4 | let bimap f g = function 5 | | Left x -> Left (f x) 6 | | Right y -> Right (g y) 7 | 8 | let l = Left 1 9 | let r = Right "hello" 10 | 11 | Either.bimap id id l = l //true 12 | Either.bimap id id r = r //true 13 | 14 | -------------------------------------------------------------------------------- /8 Functoriality/8.3 Functorial Algebraic Data Types.fsx: -------------------------------------------------------------------------------- 1 | //Type classes workaround for fmap : use statically resolved type 2 | let inline fmap< ^a, ^b, ^c, ^d when ^a : (static member fmap: (^b -> ^c) * ^a -> ^d) > f (x:^a) : ^d = 3 | (^a : (static member fmap: (^b -> ^c) * ^a -> ^d) (f,x)) 4 | 5 | // You can use operator which is less brainer to define. 6 | // Here, in fsharp map (map is widely used) is fmap but in haskell map is reserved for list. 7 | let inline map f x = f x 8 | 9 | type Maybe<'a> = Nothing | Just of 'a 10 | with 11 | static member fmap (f, x) = match x with Nothing -> Nothing | Just x' -> Just (f x') 12 | static member () (f, (x:Maybe<'a>)) = Maybe<'a>.fmap (f, x) 13 | 14 | //Curried Version of Maybe functions 15 | module Maybe = 16 | let map f (x:Maybe<'a>) = Maybe<'a>.fmap(f, x) 17 | 18 | //Behind, the maybe should have the uncurried one.. 19 | fmap (id) (Just 1) = Just 1 20 | 21 | id (Just 1) = Just 1 22 | 23 | let inline bimap< ^a, ^b, ^c, ^d, ^e, ^f when ^a : (static member bimap: (^b -> ^c) * (^d -> ^e) * ^a -> ^f) > f g (x:^a) : ^f = 24 | (^a : (static member bimap: (^b -> ^c) * (^d -> ^e) * ^a -> ^f) (f,g,x)) 25 | 26 | type Either<'a, 'b> = Left of 'a | Right of 'b 27 | with static member bimap (f,g,x) = 28 | match x with 29 | | Left x -> Left (f x) 30 | | Right y -> Right (g y) 31 | 32 | bimap id id (Left 5) = Left 5 33 | 34 | type Const<'c, 'a> = Const of 'c 35 | with static member fmap ((_:'a -> 'b), ((Const v):Const<'c, 'a>)) : Const<'c, 'b> = Const v 36 | 37 | type Identity<'a> = Identity of 'a 38 | with static member fmap (f, Identity x) = Identity (f x) 39 | 40 | type BiComp<'a> = BiComp of 'a 41 | 42 | module BiComp = 43 | let inline bimap f1 f2 (BiComp x) = BiComp ((bimap (fmap f1) (fmap f2)) x) 44 | 45 | module Maybe2 = 46 | let inline just (x:'a) : Either, Identity<'a>> = Right (Identity x) 47 | 48 | BiComp (Maybe2.just 2) |> BiComp.bimap id id = BiComp (Right (Identity 2)) //true 49 | 50 | 51 | //Personal example : the traverse one : 52 | module Identity = 53 | let map f (Identity x) = Identity (f x) 54 | 55 | module Const = 56 | let map (_:'a->'b) ((Const v):Const<'c, 'a>) : Const<'c, 'b> = Const v 57 | 58 | module Either = 59 | let bimap f g x = 60 | match x with 61 | | Left l -> Left (f l) 62 | | Right r -> Right (g r) 63 | 64 | BiComp (Maybe2.just 2) |> (fun (BiComp x) -> Either.bimap (Const.map ignore) (fun x -> Identity.map id x) x) |> BiComp = BiComp (Right (Identity 2)) //true 65 | -------------------------------------------------------------------------------- /8 Functoriality/8.4 Functors in C#.csx: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | static class BiFunctor 4 | { 5 | static ValueTuple bimap(Func f, Func g, ValueTuple x) 6 | { 7 | var (y, z) = x; 8 | return (f(y), g(z)); 9 | } 10 | } 11 | 12 | interface Tree { } 13 | 14 | class Node : Tree 15 | { 16 | public Tree Left { get; } 17 | public Tree Right { get; } 18 | 19 | public Node(Tree left, Tree right) 20 | { 21 | Left = left; 22 | Right = right; 23 | } 24 | } 25 | 26 | class Leaf : Tree 27 | { 28 | public T Value { get; } 29 | 30 | public Leaf(T value) 31 | { 32 | Value = value; 33 | } 34 | } 35 | 36 | static class TreeExtension 37 | { 38 | public static Tree map (Func f, Tree x) 39 | { 40 | switch (x) 41 | { 42 | case Leaf l : return new Leaf(f(l.Value)); 43 | case Node n : 44 | return new Node(map(f, n.Left), map(f, n.Right)); 45 | } 46 | throw new NotImplementedException("absurd"); 47 | } 48 | 49 | public static Tree tree(int depth) 50 | { 51 | Tree build(int d, Tree r) 52 | { 53 | if (d == 0) return r; 54 | else return build(d - 1, new Node(new Leaf(d), r)); 55 | } 56 | return build(depth, new Leaf(depth)); 57 | } 58 | } 59 | 60 | var t1 = TreeExtension.tree(100000); //it works on console app not in csharp interactive. 61 | 62 | var r = map(x => x, t1); //It fail with StackOverflow even if your are in release x64. 63 | 64 | var t11 = TreeExtension.tree(10); //with less depth it works 65 | 66 | //For a better experience, see the fsharp one in with taicall optimization. 67 | 68 | //Personal notes, this is the tree structure for x64 release mode in csharp. For fsharp this implementation works perfectly. 69 | 70 | class OptimizedNode : Tree 71 | { 72 | public T Head { get; } 73 | public Tree Tail { get; } 74 | 75 | public OptimizedNode(T head, Tree tail) 76 | { 77 | Head = Head; 78 | Tail = tail; 79 | } 80 | } 81 | 82 | Tree tree(int depth) 83 | { 84 | Tree build(int d, Tree r) 85 | { 86 | if (d == 0) return r; 87 | else return build(d - 1, new OptimizedNode(d, r)); 88 | } 89 | 90 | return build(depth, new Leaf(depth)); 91 | } 92 | 93 | var t2 = tree(1000000); //this works on app console with release x64. 94 | 95 | -------------------------------------------------------------------------------- /8 Functoriality/8.4 Functors in F#.fsx: -------------------------------------------------------------------------------- 1 | type Tree<'a> = Leaf of 'a | Node of Tree<'a> * Tree<'a> 2 | 3 | module BiFunctor = 4 | let bimap f g (x, y) = (f x, g y) 5 | 6 | module Tree = 7 | //Not a production code. 8 | let rec map f = function 9 | | Leaf x -> Leaf (f x) 10 | | Node (t1,t2) -> Node (BiFunctor.bimap (map f) (map f) (t1, t2)) 11 | 12 | let tree depth = 13 | let rec build depth r = 14 | if depth = 0 then r 15 | else build (depth - 1) (Node (r, Leaf depth)) 16 | build depth (Leaf depth) 17 | 18 | let t = tree 100000 19 | 20 | Tree.map id t //Process is terminated due to StackOverflowException. Like said the book, this code is not optimized. 21 | 22 | //Personal notes 23 | //Here is an optimized tree structure with head and tail structure on Node case. 24 | type OptimizedTree<'a> = Leaf of 'a | Node of 'a * OptimizedTree<'a> 25 | 26 | module OptimizedTree = 27 | //This function make traversable possible with tailcall optimization 28 | let rec fold folder state = function 29 | | Leaf l -> folder state l 30 | | Node (v, t) -> 31 | //This is a tail call. Here we don't have 2 tree to map. 32 | //We can fold the left value and continue to fold only on right. 33 | fold folder (folder state v) t 34 | 35 | //reuse fold for map with head as neutral because Tree should have at least one Leaf. 36 | let map f = function 37 | | Leaf l -> Leaf (f l) 38 | | Node (v, t) -> fold (fun state x -> Node (f x, state)) (Leaf v) t 39 | 40 | let reverse t = map id t 41 | 42 | let tree2 depth = 43 | let rec build depth r = 44 | if depth = 0 then r 45 | else build (depth - 1) (Node (depth, r)) 46 | build depth (Leaf depth) 47 | 48 | let t2 = tree2 100000 49 | 50 | //We have to reverse the tree because fold/map reverse the order 51 | OptimizedTree.map id t2 |> OptimizedTree.reverse = t2 //true with stack overflow. 52 | -------------------------------------------------------------------------------- /8 Functoriality/8.5 The Writer Functor.fsx: -------------------------------------------------------------------------------- 1 | type Writer<'a> = Writer of 'a * string 2 | 3 | module Writer = 4 | let (>=>) f g = 5 | fun x -> 6 | let (Writer (y, s1)) = f x 7 | let (Writer (z, s2)) = g y 8 | Writer (z, s1 + s2) 9 | 10 | let ret x = Writer (x, "") 11 | let fmap f = id >=> (f >> ret) 12 | 13 | let w1 = Writer.ret 1 14 | let w2 = Writer.ret 2 15 | 16 | -------------------------------------------------------------------------------- /8 Functoriality/8.6 Covariant and Contravariant Functors.fsx: -------------------------------------------------------------------------------- 1 | type Reader<'r, 'a> = 'r -> 'a 2 | 3 | let fmap f g = f << g 4 | 5 | type Op<'r, 'a> = 'a -> 'r 6 | 7 | // Introduction of opposite (Contravariant functor) : Impossible to implement the following fmap without it. 8 | // We have to found an opposite Functor of 'a -> 'b to 'b -> 'a. 9 | module Op = 10 | let fmap (f:'a -> 'b) (x:'a -> 'r) : ('b -> 'r) = 11 | failwith "not yet implemented" 12 | 13 | module Contravariant = 14 | let flip f y x = f x y 15 | let contramap f g = flip (<<) f g 16 | 17 | let isEven x = x % 2 = 0 18 | let headIsEven = Contravariant.contramap List.head isEven 19 | headIsEven [0..10] 20 | 21 | //Contravariant Functor (map input) is an Opposite Covariant Functor (map output). 22 | 23 | //Personal notes : 24 | 25 | //The (>>) operator is the opposite of (<<) operator ? 26 | 27 | open Contravariant 28 | 29 | let f = (contramap List.head) >> (contramap isEven) 30 | f [0..10] 31 | -------------------------------------------------------------------------------- /8 Functoriality/README.md: -------------------------------------------------------------------------------- 1 | # 8 Functoriality 2 | 3 | ## 8.1 Bifunctors (Personal note) 4 | In this sample, fsharp style has been used: define a type (BiFunctor) and define associated functions inside a module (BiFunctor). 5 | In fsharp, all primitives are organized like this. 6 | 7 | ## 8.2 Product and Coproduct Bifunctors 8 | To define a set as a monoidal category with respect to Cartesian product, This chapter explains very well how to define : 9 | - the binary operation (+ or mappend) as bifunctor 10 | - and zero/mempty as unit ```()``` 11 | 12 | ## 8.3 Functorial Algebraic Data Types 13 | 14 | ### Personal notes 15 | In this sample, if we want to simulate a type class with fsharp style, we have to define a fmap function with [Statically Resolved Type Parameters](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/generics/statically-resolved-type-parameters). 16 | The question is when and why ? 17 | - When you want to compose types and reuse in depth function composition like : 18 | ```newtype BiComp bf fu gu a b = BiComp (bf (fu a) (gu b))``` 19 | 20 | ```instance (Bifunctor bf, Functor fu, Functor gu) => Bifunctor (BiComp bf fu gu) where bimap f1 f2 (BiComp x) = BiComp ((bimap (fmap f1) (fmap f2)) x)``` 21 | 22 | The compiler chooses the best overloading of fmap and bimap. 23 | 24 | //Link : https://stackoverflow.com/questions/39065724/using-statically-resolved-type-parameters-is-it-possible-to-call-class-method-wi 25 | The bad news : This approach does not work with curried functions.. It would be great if we have it to attach map functions defined in different modules (List, Option and so on..) 26 | It is not possible to create those member in type Augmentation 27 | 28 | You can write this function case per case. Maybe there was one or two instance behind in the program and the traverse one is easier to write first. 29 | 30 | ## 8.4 Functors in C++ (Personal notes) 31 | I will translate this example from C++ to F# and C# 32 | 33 | ### BiFunctor / Production code ready 34 | I added a real production code for tree. The unoptimized is interesting to introduce the BiFunctor. 35 | The main difference between an optimized one and a product one are : 36 | - Product is easy to build mappend with tuple but the traverse is [harder](https://hackage.haskell.org/package/bifunctors-5/docs/Data-Bifunctor.html#t:Bifunctor) : you need [clown](https://hackage.haskell.org/package/bifunctors-5/docs/Data-Bifunctor-Clown.html#t:Clown) and [joker](https://hackage.haskell.org/package/bifunctors-5/docs/Data-Bifunctor-Joker.html#t:Joker) 37 | - Head and tail : fold is easy to write but the mappend is harder because you don't have product and bifunctor to do it quickly. 38 | 39 | ### C# (Personal notes) 40 | I translate the product one to use a bifunctor and the optimized one but the csharp interactive is quite limited. See the fsharp one to have a full interactive experience. 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 @cboudereau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # category-theory-for-dotnet-programmers 2 | This repo contains c++ / haskell samples from Bartosz Milewski's book ([Category Theory for Programmers](https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface/)) converted to csharp and fsharp 3 | 4 | ## Why 5 | If you are curious about functional programming with dotnet background, you already may know [Domain modeling made functional](https://pragprog.com/book/swdddf/domain-modeling-made-functional) that could help you to build your first functional programming based app. 6 | The [Category Theory for programmers](https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface/), is quite interresting for programmers having a first experience in fsharp or csharp (in .net) who want to use Haskell or enhance their FSharp implementation. 7 | 8 | Fsx and Csx are scripts files executed respectively into Fsharp interactive and Csharp interactive shipped with Visual Studio Community at least. 9 | 10 | /!\ There is a problem with csx : to transform statement as expression (to display the value) you have to not ending your line with ';' causing compilation error on multiple line. 11 | If you encounter compilation error on csx, try to execute it line by line. If the code does not compile send a PR or a message. 12 | 13 | # How to use it ? 14 | 15 | Read the [Category Theory for Programmers](https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface/) and the annotations corresponding to the chapter into vs code or visual studio. 16 | 17 | You can use it like a sandbox and try by yourself Challenges (challenges are not translated for more fun ;)). Only Haskell/C++ samples are translated. 18 | 19 | I will try to add converted examples constantly but pull requests are accepted. 20 | The format is pretty simple, one folder per each chapter and 2 scripts (fsharp and csharp) inside. --------------------------------------------------------------------------------