Last active
October 5, 2021 18:55
-
-
Save julian-a-avar-c/65af877504e7976a1919f08c49fca425 to your computer and use it in GitHub Desktop.
I've been learning some FP, so I wanted to teach my friends some of the cool stuff I've been learning :)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Linq; | |
using System.Text.Json; | |
using System.Text.RegularExpressions; | |
using System.Collections.Generic; | |
namespace FunctionalProgrammingForCSharp { | |
class Program { | |
static void title(string title_name, string separator = "\n----\n") { | |
Console.Write(separator + $" {title_name}" + separator); | |
} | |
static void block(string text, int indentation = 4) { | |
Console.WriteLine("\n" + string.Join("\n", text | |
.Split("\n") | |
.Select(line => line.Length >= indentation ? line[indentation..] : line) | |
.Select(line => "| " + line) | |
)); | |
} | |
static void label(string text) { | |
Console.WriteLine($"\n- {text}"); | |
} | |
static void log(string text) { Console.WriteLine($"Log> {text}"); } | |
static void log<T>(T t) { log(to_s<T>(t)); } | |
static void log<T>(List<T> t) { log(to_s<T>(t)); } | |
static string to_s<T>(T t) { return t.ToString(); } | |
static string to_s<T>(List<T> l) { | |
return $"[{string.Join(", ", l.Select(e => to_s<T>(e)))}]"; | |
} | |
static void end() { | |
Console.Write("\n[END OF CHAPTER]"); | |
} | |
static T clone<T>(T thing) => | |
JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(thing)); | |
static void newline() { Console.WriteLine(); } | |
static void Main(string[] args) { | |
title("Functional Programming for C#", "\n========\n"); | |
// Feel free to un/comment out the lines chapter by chapter :D | |
// You may see some of the chapters being shuffled around or renamed, | |
// don't worry, I'm trying to improve the reading experience :P | |
Chapter_1("Chapter 1 - Pure Functions"); | |
// Chapter_2("Chapter 2 - Functions as First Class Citizens"); | |
// Chapter_3("Chapter 3 - Imperative vs Declarative"); | |
// Chapter_4("Chapter 4 - Immutability"); | |
// Chapter_5("Chapter 5 - Currying"); | |
// Chapter_6("Chapter 6 - Function Composition"); | |
// Chapter_7("Chapter 7 - Avoid Primitive Obsession"); | |
// Chapter_8("Chapter 8 - Option Type"); | |
// Epilogue(); | |
} | |
static void Chapter_1(string title_name = "Chapter 1 - Pure Functions") { | |
title(title_name); | |
block(@" | |
Much like how OOP is all about objects, functional programming | |
is all about functions. OOP tries to encapsulate things so that | |
each part does not need to know about each other which ends | |
looking very elegant to the end user : car1.drive() as opposed | |
to drive(car1), and then making sure there's an overload for | |
every kind of function. | |
However, FP is based on different principles than OOP. | |
Let's start with pure functions. We'll say that we have a list | |
of numbers. We first need to double them, then we need to sum | |
them. | |
An impure way of doing so might be: | |
-- ex1 | |
"); | |
List<float> nums = new List<float>() {1f, 2f, 3f, 4f, 5f}; | |
void double_nums_v1() { | |
for (int i = 0; i < nums.Count; i ++) { | |
float oldNum = nums[i]; | |
float newNum = oldNum * 2; | |
nums[i] = newNum; | |
} | |
} | |
float sum_v1() { | |
float total = 0; | |
for (int i = 0; i < nums.Count; i++) { | |
float num = nums[i]; | |
total += num; | |
} | |
return total; | |
} | |
double_nums_v1(); | |
float total_v1 = sum_v1(); | |
label("nums"); | |
log(nums); | |
label("total_v1"); | |
log(total_v1); | |
block(@" | |
Let's annalyse this for a bit. First we doubled the numbers, | |
great, then we got the sum and the count of how many items at | |
the same time, double great! Now let's get the original array, | |
but we can't :( because it doesn't exist anymore, we merged it | |
the double numbers. | |
But when I called `double_nums()` nothing told me I was going | |
to change the variable that I needed later. So now I have a bug | |
where a value that I needed to use later is changed and I have | |
hundreds of lines and I'm gonna have to sit through them until | |
I find the line that changed the variable. | |
Instead we (when permissible) we should try to never change a | |
value that is not owned by the function. We must not have side | |
effects, because then we have to trust that whoever is naming | |
the functions is doing so correctly all the time (this of | |
course includes our past selves). | |
In order for a function to be pure, however, it also need to be | |
completely independent from outside sources. This is so that | |
they always return the same value. `sum()` for example does not | |
modify a value outside of itself, however it is based on a | |
value outside of it's scope, and therefore it doesn't have | |
referential transparency. | |
This means that if you call the same function twice, you will | |
get different values. | |
For instance: | |
-- ex2 | |
"); | |
int num = 0; | |
int add_v1(int x) { | |
num += x; | |
return num; | |
} | |
label("add_v1(3)"); | |
log(add_v1(3)); | |
label("add_v1(3)"); | |
log(add_v1(3)); | |
label("add_v1(3)"); | |
log(add_v1(3)); | |
block(@" | |
As seen, even though we called the same function 3 times with | |
the same value, we got different answers, which is ridiculous! | |
Sometimes, it's needed such as a random number generator, or | |
when you need to track the state of something. | |
So we can see that pure functions are not *necessary* all the | |
time, but they are really useful because they are not changing | |
hidden variables which we don't know anything about, and they | |
always return the same result. | |
Therefore, by convention, any function that returns should be | |
pure, and any void function should contain *side* effects and | |
mutations. Again, cirscumstances which require otherwise will | |
appear, and you don't have to refactor all your functions to be | |
pure, but it's nice to separate them. | |
Finally let's rewrite the above code in a *purer* way. | |
-- ex3 | |
"); | |
// redefine, since it's still in scope | |
nums = new List<float>() {1f, 2f, 3f, 4f, 5f}; | |
List<float> double_nums_v2(List<float> original_ns) { | |
List<float> new_ns = new List<float>(); | |
for (int i = 0; i < original_ns.Count; i ++) { | |
float oldNum = original_ns[i]; | |
float newNum = oldNum * 2; | |
new_ns.Add(newNum); | |
} | |
return new_ns; | |
} | |
float sum_v2(List<float> original_ns) { | |
float total = 0; | |
for (int i = 0; i < original_ns.Count; i++) { | |
float num = original_ns[i]; | |
total += num; | |
} | |
return total; | |
} | |
List<float> doubled_nums = double_nums_v2(nums); | |
float total_v2 = sum_v2(doubled_nums); | |
label("nums"); | |
log(nums); | |
label("doubled_nums"); | |
log(doubled_nums); | |
label("total_v2"); | |
log(total_v2); | |
end(); | |
} | |
static void Chapter_2(string title_name = "Chapter 2 - Functions as First Class Citizens") { | |
title(title_name); | |
block(@" | |
Here's something which you might have not have thought of much | |
if you started with C#. Functions can be values. That's one of | |
the main concepts behind FP. Luckily for us, that functionality | |
was added in C#3 in the form of delegates and lambda functions. | |
Let's say that we have a 2 dimensional array, and we need to | |
loop through it and add 42 to every value, and multiply by | |
11 every value, and store both in it's own variable. | |
We'd write something like this: | |
-- ex1 | |
"); | |
List<List<int>> important2DList = new List<List<int>>() { | |
new List<int>() { 1, 2, 3 }, | |
new List<int>() { 4, 5, 6 }, | |
new List<int>() { 7, 8, 9 } | |
}; | |
List<List<int>> plus42_val = clone(important2DList); | |
for (int i = 0; i < plus42_val.Count; i++) { | |
for (int j = 0; j < plus42_val[i].Count; j++) { | |
plus42_val[i][j] += 42; | |
} | |
} | |
List<List<int>> multiplyBy11_val = clone(important2DList); | |
for (int i = 0; i < multiplyBy11_val.Count; i++) { | |
for (int j = 0; j < multiplyBy11_val[i].Count; j++) { | |
multiplyBy11_val[i][j] *= 11; | |
} | |
} | |
label("important2DList"); | |
log($"[{string.Join(", ", important2DList.Select(e => to_s(e)))}]"); | |
label("plus42_val"); | |
log($"[{string.Join(", ", plus42_val.Select(e => to_s(e)))}]"); | |
label("multiplyBy11_val"); | |
log($"[{string.Join(", ", multiplyBy11_val.Select(e => to_s(e)))}]"); | |
block(@" | |
You might notice that we are repeating a lot of code. In fact, | |
I didn't wanna type it again, so I just copy pasted. Only one | |
line changed, yet we have only changed one line. It feels very | |
dumb, and I don't know if you've ever done something like that, | |
but I definitely have, and it's so frustrating because I didn't | |
know how to fix it so that I didn't repeat so much code. | |
Sure, you can put it inside of a function and then add an if | |
else statement, but that's so much complexity to do something | |
so simple. | |
So let's take a look at how we may fix that. As explained | |
before we need to use functions as values. So let's take a look | |
at how to do that. I mean, there is no `function` type right? | |
Well, actually there is XD. It's called delegate in C#, but | |
sadly it requires class scope, so I'm not going to be doing | |
that. Instead we can use `System.Func` and `System.Action`. | |
They both return a delegates, however, an `Action` returns | |
void, while a `Func` returns something. Since we are trying to | |
write pure functions as stated in Chapter 1, we will be mainly | |
using `Func`, but if you need/want side effects, use `Action`. | |
Let's start on how to use it: | |
-- ex2 | |
"); | |
Func<int, int, int> add_two_nums = delegate(int a, int b) { | |
return a + b; | |
}; | |
label("add_two_nums"); | |
log(add_two_nums(1, 2)); | |
block(@" | |
Let's first observe the signature, or the type of | |
`add_two_nums`, it being: `Func<int, int, int>`. | |
As you might have guessed, the last generic argument is the | |
return type (by generic argument, I mean the things between the | |
angle brackets <,> which are also called type arguments) while | |
the rest are the argument types. Of course, in an `Action` you | |
don't have a return type, so the same argument signature would | |
mean that you have 3 arguments. | |
Either way, you have 2 inputs 1 output, and then you set it to | |
a delegate, or a function. | |
You might think, so what? Well since it's a value (just like | |
any other value) you can change it, let's do so: | |
-- ex3 | |
"); | |
// we can't change the type since it's already defined | |
add_two_nums = delegate(int a, int b) { | |
return a * b + 42; | |
}; | |
label("add_two_nums"); | |
log(add_two_nums(1, 2)); | |
block(@" | |
And now the behavior is completely changed. You can use this if | |
for example you have a bunch of entities and you want to change | |
their behavior mid-way, you simply change the function and | |
that's it! | |
Another thing you might have noticed is that since it's now a | |
value, that means you can pass it to another function just like | |
any other value, and then pass those to other functions: | |
-- ex4 | |
"); | |
// let's go back to the previous definition | |
add_two_nums = delegate(int a, int b) { return a + b; }; | |
Func<Func<int, int, int>, int, int, int> multiply_two_nums_with_add_definition = delegate( | |
Func<int, int, int> how_to_add, int a, int b | |
) { | |
int total = 0; | |
for (int i = 1; i <= a; i++) { total = how_to_add(total, b); } | |
return total; | |
}; | |
label("multiply_two_nums_with_add_definition(add_two_nums, 3, 4)"); | |
log(multiply_two_nums_with_add_definition(add_two_nums, 3, 4)); | |
block(@" | |
If by this point your mind is not blow, you have no soul. | |
`multiply_two_nums_with_add_definition` has no idea how to sum, | |
so we pass that function so it can do it's job. | |
You should now try to solve ex1. | |
The solution is pretty simple: | |
-- ex5 | |
"); | |
Func<List<List<int>>, Func<int, int>, List<List<int>>> loop2D_v1 = delegate( | |
List<List<int>> list, | |
Func<int, int> operation | |
) { | |
for (int i = 0; i < list.Count; i++) { | |
for (int j = 0; j < list[i].Count; j++) { | |
list[i][j] = operation(list[i][j]); | |
} | |
} | |
return list; | |
}; | |
Func<int, int> plus42 = delegate(int a) { return a + 42; }; | |
Func<int, int> multiplyBy11 = delegate(int a) { return a * 11; }; | |
List<List<int>> plus42_v1 = loop2D_v1(important2DList, plus42); | |
List<List<int>> multiplyBy11_v1 = loop2D_v1(important2DList, multiplyBy11); | |
block(@" | |
As you can see above, we've got rid of the repetitiveness and | |
if the function is not complex enough we can even put it on the same line: | |
-- ex6 | |
"); | |
List<List<int>> plus42_v2 = loop2D_v1(important2DList, delegate(int a) { return a + 42; }); | |
List<List<int>> multiplyBy11_v2 = loop2D_v1(important2DList, delegate(int a) { return a * 11; }); | |
block(@" | |
Finally, we can use lambda notation, which is simpler: | |
-- ex7 | |
"); | |
Func<List<List<int>>, Func<int, int>, List<List<int>>> loop2D_v2 = (list, operation) => { | |
for (int i = 0; i < list.Count; i++) { | |
for (int j = 0; j < list[i].Count; j++) { | |
list[i][j] = operation(list[i][j]); | |
} | |
} | |
return list; | |
}; | |
List<List<int>> plus42_v3 = loop2D_v2(important2DList, a => a + 42); | |
List<List<int>> multiplyBy11_v3 = loop2D_v2(important2DList, a => a * 11); | |
block(@" | |
And as you can see we now have one function to loop through all | |
the stuff that we had to, and two expression to dictate what | |
we're actually doing, instead of repeating that double for loop | |
twice. 13 vs 11 lines. Sure it's not that much shorter, but | |
it's arguably easier to understand and you are not repeating | |
code. | |
Of course, much like with pure functions, this technique should | |
be used with discretion, but it's a very powerful technique if | |
used correctly. | |
"); | |
} | |
static void Chapter_3(string title_name = "Chapter 3 - Imperative vs Declarative") { | |
title(title_name); | |
block(@" | |
Let's use the last example used | |
-- ex1 | |
"); | |
List<List<int>> important2DList = new List<List<int>>() { | |
new List<int>() { 1, 2, 3 }, | |
new List<int>() { 4, 5, 6 }, | |
new List<int>() { 7, 8, 9 } | |
}; | |
Func<List<List<int>>, Func<int, int>, List<List<int>>> loop2D_v1 = (list, operation) => { | |
for (int i = 0; i < list.Count; i++) { | |
for (int j = 0; j < list[i].Count; j++) { | |
list[i][j] = operation(list[i][j]); | |
} | |
} | |
return list; | |
}; | |
List<List<int>> plus42_v1 = loop2D_v1(important2DList, a => a + 42); | |
List<List<int>> multiplyBy11_v1 = loop2D_v1(important2DList, a => a * 11); | |
block(@" | |
As you can see from | |
``` | |
for (int i = 0; i < list.Count; i++) { | |
for (int j = 0; j < list[i].Count; j++) { | |
``` | |
that monstrosity, in order to accomplish our task of adding 42 | |
and then multiplying by 11, we can't do any of that before we | |
tell the computer how to go through the list. | |
We must teach the computer how to start it, how to stop, how to | |
loop, how to vary it's speed. It's a pain in the neck, | |
sometimes you might even forget what you were doing while | |
writing all of the boilerplate code (I know I have). | |
From that, it'd be super nice if there was a method that would | |
just loop through everything in a relatively fast way, and make | |
*it* figure out how to loop instead of doing so ourselves. | |
Luckily for us, we know how to use functions as first class | |
citizens. Let's call our function `ForEach()` | |
-- ex2 | |
"); | |
newline(); | |
void ForEach(List<int> list, Action<int> task) { | |
for (int i = 0; i < list.Count; i++) { task(list[i]); } | |
} | |
List<int> nums = new List<int>() {1, 2, 3, 4, 5}; | |
ForEach(nums, num => Console.WriteLine(num + 1)); | |
block(@" | |
Sadly, we can't modify the original list, there are ways of | |
course, like passing a reference to the item in our `ForEach` | |
instead of passing the value, but that's outside of our scope. | |
For now, know that you can't modify the original. | |
BUT, you can return a new `List` and assign it to the same one | |
once it returns. Of course we try not to do that since with | |
purity comes immutability, but as said before, this, much like | |
OOP, are only guidelines, you can make your code as horrible as | |
you want in practice. | |
So let's make a function that return a modified list instead of | |
doing side effects only and call it Map: | |
-- ex3 | |
"); | |
List<int> Map_intonly(List<int> list, Func<int, int> function) { | |
list = clone(list); | |
for (int i = 0; i < list.Count; i++) { | |
list[i] = function(list[i]); | |
} | |
return list; | |
} | |
List<int> result1 = Map_intonly(nums, num => num + 1); | |
label("result1"); | |
log(result1); | |
label("nums"); | |
log(nums); | |
block(@" | |
Don't worry about the `clone()` function, it'll be irrelevant | |
by the end of the chapter ;) | |
We loop through the list, then we set the value of each element | |
essentially applying a _transform_ and then we return it so | |
that it can be used by someone. | |
And we could just hide the implementation behind the curtains | |
and no one would care about it, you can just use the function | |
which is excellent because it means you won't ever need to | |
touch a for loop unless you really care about performance and | |
need to control every aspect of how you loop. | |
In that sense this is like C# vs Assembler (google it if | |
you've never seen assembler code, it's disgusting). On one hand | |
you have a one line representation of a for loop which although | |
may not be the fastest it's good enough for most tasks, on the | |
other you have this for loop that you have to type like 40 | |
characters to get going, and sometimes you misspell something | |
and you end up an hour hung up with something you really | |
shouldn't even be worrying about. You are sacrificing a little | |
bit of speed with readability and ease of use. | |
Let's now go all the way around and implement our `loop2D` with | |
our map function. But first we need to make a generic version | |
(meaning that it can use any type, not just int like before) | |
-- ex4 | |
"); | |
List<T> Map<T>(List<T> list, Func<T, T> operation) { | |
list = clone(list); | |
for (int i = 0; i < list.Count; i++) { | |
list[i] = operation(list[i]); | |
} | |
return list; | |
} | |
block(@" | |
Now we can pass any kind of list and as long as the operation | |
return the same type everything is fine :D | |
Let's create our loop2D now: | |
-- ex5 | |
"); | |
Func<List<List<int>>, Func<int, int>, List<List<int>>> loop2D = (list, operation) => | |
Map(list, inner_list => Map(inner_list, item => operation(item))); | |
List<List<int>> plus42_v2 = loop2D(important2DList, a => a + 42); | |
List<List<int>> multiplyBy11_v2 = loop2D(important2DList, a => a * 11); | |
block(@" | |
Would you look at this, we started with 13 lines, and now we | |
have 4, and only because `loop2D` doesn't fit in one line, | |
otherwise, it'd fit in 3. | |
But luckily for us, we don't need to define Map, because it's | |
already defined only with a differnet name: `Select`. However, | |
in order to use it, you need to be `using System.Linq`. | |
Once you're ready you can type code like this without defining | |
any helper functions: | |
-- ex6 | |
"); | |
Func<List<List<int>>, Func<int, int>, List<List<int>>> select2D = (list, operation) => | |
list.Select(inner_list => inner_list.Select(item => operation(item)).ToList()).ToList(); | |
block(@" | |
There are 3 big functions which are used on a day to day basis, | |
`Select`, `Aggregate`, and `Where`, which is equivalent to | |
`map`, `reduce`, and `filter`. | |
`Select`/`map` maps every item by applying a transformation. | |
`Aggregate`/`reduce` reduces the items so that there's only one | |
left. It uses an accumulator, and the current item. | |
`Where`/`filter` filters the list. | |
"); | |
} | |
class Point_v1 { | |
public int x { get; private set; } | |
public int y { get; private set; } | |
public Point_v1(int x, int y) { | |
this.x = x; | |
this.y = y; | |
} | |
public void MoveBy(int x, int y) { | |
this.x += x; | |
this.y += y; | |
} | |
} | |
class Point_v2 { | |
private int x { get; } | |
private int y { get; } | |
public Point_v2(int x, int y) { | |
this.x = x; | |
this.y = y; | |
} | |
public Point_v2 MoveBy(int x, int y) { | |
return new Point_v2(this.x + x, this.y + y); | |
} | |
} | |
class Point_v3 { | |
private int x { get; } | |
private int y { get; } | |
public Point_v3(int x, int y) { | |
this.x = x; | |
this.y = y; | |
} | |
public (int x, int y) MoveBy(int x, int y) { | |
return (this.x + x, this.y + y); | |
} | |
} | |
static void Chapter_4(string title_name = "Chapter 4 - Immutability") { | |
title(title_name); | |
block(@" | |
Immutability means that FP prefers to not mutate the values of | |
a variable, instead creating a new value. | |
This means, instead of using your class to modify itself, try | |
to return a new object. | |
-- ex1 | |
"); | |
// Point_v1 | |
Point_v1 p_v1 = new Point_v1(3, 4); | |
p_v1.MoveBy(5, 6); | |
block(@" | |
In our fist example, we are encapsulating `x` and `y`, cannot | |
access them directly and need to use `MoveBy` in order to set | |
new positions. This is the OOP way of doing things. | |
FP however dictates that you should not use hidden states, | |
functions must be pure, and therefore you shouldn't be causing | |
side effects like `Point_v1.MoveBy` does. | |
-- ex2 | |
"); | |
// Point_v2 | |
Point_v2 p_v2 = new Point_v2(3, 4); | |
Point_v2 new_p_v2 = p_v2.MoveBy(5, 6); | |
block(@" | |
In our second example `MoveBy` is now returning a new object, | |
which is great for FP as that means more immutability and more | |
pure functions. But it also means more processing, since | |
objects can become quite memory intensive. | |
We could also create another `MoveBy` which returns the | |
parameters necessary to create the new object. | |
-- ex3 | |
"); | |
// Point_v3 | |
Point_v3 p_v3 = new Point_v3(3, 4); | |
var new_p_v3_args = p_v3.MoveBy(5, 6); | |
Point_v3 new_p_v3 = new Point_v3(new_p_v3_args.x, new_p_v3_args.y); | |
block(@" | |
And so we can keep our immutability, and also not spam out ram | |
until needed. | |
"); | |
} | |
static void Chapter_5(string title_name = "Chapter 5 - Currying") { | |
title(title_name); | |
block(@" | |
In the previous chapters we saw how function are data, just | |
like strings, or ints. But just like we can pass functions as | |
arguments, we can also return functions. That is what we call | |
currying. | |
Let's say that we have a complex function, and we need to call | |
it with multiple values, multiple times. | |
-- ex1 | |
"); | |
Func<int, string, bool, short, long, float, int> complexFunc = ( | |
vA, vB, vC, vD, vE, vF | |
) => { | |
// assume complex calculation | |
return 0; | |
}; | |
int vA = 0; | |
string vB = "second value"; | |
bool vC = false; | |
short vD = 6; | |
long vE0 = 2500000; | |
long vE1 = 2522222; | |
float vF1 = 4.20f; | |
float vF2 = 69.69f; | |
float vF3 = 21.00f; | |
complexFunc(vA, vB, vC, vD, vE0, vF1); | |
complexFunc(vA, vB, vC, vD, vE0, vF2); | |
complexFunc(vA, vB, vC, vD, vE0, vF3); | |
complexFunc(vA, vB, vC, vD, vE1, vF1); | |
complexFunc(vA, vB, vC, vD, vE1, vF2); | |
complexFunc(vA, vB, vC, vD, vE1, vF3); | |
block(@" | |
We are repeating a lot of values. Also, what if `vB` is | |
calculated before, then we would have to pass around vB around | |
as parameter a bunch of times until needed, or store it as | |
global scope with a bunch of stuff which is not related. | |
On top of that we are repeating `vA`-`vD`, which is just | |
annoying. | |
So, evidently we are gonna have to do something better. Let's | |
learn how to do currying in C#. | |
-- ex2 | |
"); | |
Func<int, int, int> add_v1 = delegate(int x, int y) { | |
return x + y; | |
}; | |
int r1 = add_v1(2, 5); | |
int r2 = add_v1(2, 6); | |
int r3 = add_v1(3, 5); | |
int r4 = add_v1(3, 6); | |
// or | |
Func<int, Func<int, int>> add_v2 = delegate(int x) { | |
return delegate(int y) { | |
return x + y; | |
}; | |
}; | |
Func<int, int> add2_v1 = add_v2(2); | |
Func<int, int> add3_v1 = add_v2(3); | |
r1 = add2_v1(5); | |
r2 = add2_v1(6); | |
r3 = add3_v1(5); | |
r4 = add3_v1(6); | |
block(@" | |
As you can see currying requires a bit more setup, luckily, | |
lambdas are here to solve our problem, in addition, we can use | |
the `var` type, which essentially makes C# do the hard job of | |
guessing what the type will be so that we don't have to worry | |
about it. | |
-- ex3 | |
"); | |
Func<int, Func<int, int>> add_v3 = x => y => x + y; | |
var add2_v2 = add_v2(2); | |
var add3_v2 = add_v2(3); | |
r1 = add_v3(3)(5); | |
r2 = add_v3(3)(6); | |
r3 = add_v3(3)(5); | |
r4 = add_v3(3)(6); | |
// or | |
r1 = add2_v2(5); | |
r2 = add2_v2(6); | |
r3 = add3_v2(5); | |
r4 = add3_v2(6); | |
block(@" | |
First, just because we have the option of currying, doesn't | |
mean that we have to set up the function. The function | |
definition can separate the parameters with arrows (=>) instead | |
of commas (,), and you just gained a lot of flexibility. | |
But we don't always have the time, or the wants to create | |
currying functions, but it'd still be nice if we could get | |
that. | |
Luckily for us, it's not that hard to do | |
-- ex4 | |
"); | |
int multiply(int x, int y) => x * y; | |
int multBy2(int y) => multiply(2, y); | |
int multBy3(int y) => multiply(3, y); | |
r1 = multiply(2, 5); | |
r2 = multiply(2, 6); | |
r3 = multiply(3, 5); | |
r4 = multiply(3, 6); | |
// or | |
r1 = multBy2(5); | |
r2 = multBy2(6); | |
r3 = multBy3(5); | |
r4 = multBy3(6); | |
block(@" | |
Let's use currying for our first example then: | |
-- ex5 | |
"); | |
// option 1 | |
Func<int, Func<string, Func<bool, Func<short, Func<long, Func<float, int>>>>>> complexFunc1 = | |
vA => vB => vC => vD => vE => vF => { | |
// assume complex calculation | |
return 0; | |
}; | |
var func1_vABCD = complexFunc1(vA)(vB)(vC)(vD); | |
var func1_vE0 = func1_vABCD(vE0); | |
var func1_vE1 = func1_vABCD(vE1); | |
// option 2 | |
Func<int, string, bool, short, long, float, int> complexFunc2 = ( | |
vA, vB, vC, vD, vE, vF | |
) => { | |
// assume complex calculation | |
return 0; | |
}; | |
Func<long, float, int> func2_vABCD = (E, F) => complexFunc2(vA, vB, vC, vD, E, F); | |
Func<float, int> func2_vE0 = (F) => func2_vABCD(vE0, F); | |
Func<float, int> func2_vE1 = (F) => func2_vABCD(vE1, F); | |
// either: | |
func2_vE0(vF1); | |
func2_vE0(vF2); | |
func2_vE0(vF3); | |
func2_vE1(vF1); | |
func2_vE1(vF2); | |
func2_vE1(vF3); | |
block(@" | |
As you can see there are two way of doing this, you could also | |
`using` a library with a partial function, which will transform | |
any non currying function into a currying function. But from | |
where we stand that's as much as you need to know. | |
It is not necessary to use currying functions all the time, but | |
they can be very powerful when used properly. | |
"); | |
} | |
static void Chapter_6(string title_name = "Chapter 6 - Function Composition") { | |
title(title_name); | |
block(@" | |
I'm sure that in math class you might have seen something like: | |
`g(f(x)) = (g . f)(x)` [img] | |
This is called function composition, and it can be super | |
helpful. | |
img: [http://3.bp.blogspot.com/-kgATvEPETIQ/UKwME42wckI/AAAAAAAAHdM/QK8rA1bKb1U/s1600/def02.PNG] | |
Let's take a look at some code: | |
-- ex1 | |
"); | |
Func<string, char[]> reverse = x => x.Select( | |
(c, i) => (i < x.Count()) | |
? x[x.Count() - 1 - i] | |
: c | |
).ToArray(); | |
Func<char[], string> capitalize = x => new string(x.Select( | |
(c, i) => (i == 0) | |
? char.ToUpper(c) | |
: c | |
).ToArray()); | |
Func<string, int> count = x => x.Aggregate(0, (t, _) => t + 1); | |
label("new string(reverse(\"1 2 3 4 5 \"))"); | |
log(new string(reverse("1 2 3 4 5 "))); | |
label("capitalize(\"Robert\".ToArray())"); | |
log(capitalize("Robert".ToArray())); | |
label("count(\"abcdef\")"); | |
log(count("abcdef")); | |
var val1 = reverse("Radar"); | |
var val2 = capitalize(val1); | |
var val3 = count(val2); | |
// or | |
var composite = count(capitalize(reverse("Radar"))); | |
label("new string(val1)"); | |
log(new string(val1)); | |
label("val2"); | |
log(val2); | |
label("val3"); | |
log(val3); | |
label("composite"); | |
log(composite); | |
block(@" | |
As you might have guessed, all functions above have a name, so | |
luckily you won't have to make them up all the time, I'm just | |
putting them there as example. You might also have noticed that | |
I'm logging `reverse` around a `new string()`, cool note, | |
strings are actually char arrays. But enough of that for now. | |
As you might have seen, we either have to make 3 confusing | |
variables, or you can make a ton of nested variables, neither | |
of them is pretty, so knowing that functions can be treated as | |
values, can we make a function that does that for us? | |
-- ex2 | |
"); | |
Func<X, Z> compose2<X, Y, Z>(Func<Y, Z> g, Func<X, Y> f) => x => g(f(x)); | |
var reverse_then_capitalize = compose2(capitalize, reverse); | |
var reverse_then_capitalize_then_count = compose2(count, reverse_then_capitalize); | |
var composite1 = reverse_then_capitalize_then_count("john"); | |
Func<W, Z> compose3<W, X, Y, Z>( | |
Func<Y, Z> h, | |
Func<X, Y> g, | |
Func<W, X> f) => x => h(g(f(x))); | |
var reverse_capitalize_count = compose3(count, capitalize, reverse); | |
var composite2 = reverse_capitalize_count("john"); | |
label("composite1"); | |
log(composite1); | |
label("composite2"); | |
log(composite2); | |
block(@" | |
As you can see, `compose2` takes 2 functions, and `compose3` | |
takes 3 functions. Due to limitations in C# however, you'll | |
have to do a bunch of compose definition for different amount | |
of functions. Since these are local I can't overload them, but | |
you can overload as methods. Just make up to 8 and you should | |
be fine in most scenarios XD. | |
But even though we made a special function for this, it still | |
is a bit weird how we are writing the arguments backwards, this | |
is simply how compose behaves. If we want something that works | |
more intuitively we might want to use `pipe`. | |
BTW, pipe is written as `|` in most Linux bash command lines. | |
-- ex3 | |
"); | |
Func<T1, T3> pipe2<T1, T2, T3>( | |
Func<T1, T2> f1, | |
Func<T2, T3> f2) => x => f2(f1(x)); | |
var pipe_reverse_capitalize = pipe2(reverse, capitalize); | |
var pipe_reverse_capitalize_count = pipe2(reverse_then_capitalize, count); | |
var piped1 = pipe_reverse_capitalize_count("john"); | |
Func<T1, T4> pipe3<T1, T2, T3, T4>( | |
Func<T1, T2> f1, | |
Func<T2, T3> f2, | |
Func<T3, T4> f3) => x => f3(f2(f1(x))); | |
var pipe3_reverse_capitalize_count = pipe3(reverse, capitalize, count); | |
var piped2 = pipe3_reverse_capitalize_count("john"); | |
label("piped1"); | |
log(piped1); | |
label("piped2"); | |
log(piped2); | |
block(@" | |
And as you can see, pipe makes a LOT easier to read :P, but it | |
also lies on what it's doing a bit (in terms of definition). | |
That said, please don't use `compose` it's a nightmare that no | |
one wants to follow. Also, please use T1, T2... and f1, f2... | |
instead of W, X, Y, Z. Believe me, Alt-Clicking a function and | |
then finding THAT, would cause anyone to want to pull their | |
hair out. | |
Anyways, here's a cool article on that: https://www.codeproject.com/Articles/375166/Functional-programming-in-Csharp | |
"); | |
} | |
class Person1 { | |
string email; | |
public Person1(string email) { this.email = email; } | |
public string shareEmail() => this.email; | |
} | |
class Person2 { | |
string? email; | |
public Person2(string email) { | |
if (Regex.IsMatch(email, @"([A-Za-z]+[\w.]*)@([A-Za-z]+[\w.\-]*)\.[A-Za-z]")) { | |
this.email = email; | |
} else { | |
this.email = null; | |
} | |
} | |
public string shareEmail() => this.email; | |
} | |
class Person3 { | |
string email; | |
public static Person3 Factory(string email) { | |
bool valid_email = Regex.IsMatch(email, @"([A-Za-z]+[\w.]*)@([A-Za-z]+[\w.\-]*)\.[A-Za-z]"); | |
bool valid_arg1 = true; | |
bool valid_arg2 = false; | |
return (valid_arg1 && valid_arg1 && valid_arg2) | |
? new Person3(email) | |
: null; | |
} | |
public Person3(string email) { | |
this.email = email; | |
} | |
public string shareEmail() => this.email; | |
} | |
static void Chapter_7(string title_name = "Chapter 7 - Avoid Primitive Obsession") { | |
title(title_name); | |
block(@" | |
It is common amongst starting and/or lazy devs to try to use | |
primitive types all of the time since either they don't know | |
how to or don't want to make a factory method. | |
Let's go over why you would need that first :D | |
-- ex1 | |
"); | |
// Person1 | |
Person1 p1 = new Person1("[email protected]"); | |
string p1_email(Person1 p) { return p.shareEmail(); } | |
var res1 = p1_email(p1); | |
label("res1"); | |
log(res1); | |
block(@" | |
The example shown above might seem great, but it has a huge | |
flaw. What if the programmer doesn't understand what format the | |
`email` parameter has to be in? Well, we could always use some | |
regex to detect if it's right, then have the person's email be | |
null if the programmer doesn't know what he's doing: | |
-- ex2 | |
"); | |
// Person2 | |
Person2 p2 = new Person2("name@email@com"); | |
string p2_email(Person2 p) { return p.shareEmail(); } | |
var res2 = p2_email(p2); | |
label("res2"); | |
log((res2 != null) ? res2 : "null"); | |
block(@" | |
As you might have guessed now the email being incorrectly | |
formatted is no longer a problem. Now we might want to have | |
a person without an email be fine. But it happens at times when | |
the email must be valid in order to have a valid Person, yet | |
when we call the constructor, we have no way of returning | |
`null` for that. As it is now we would have to check for email | |
every time we want to use Person, or make a function to do that | |
for us, none of which sounds particularly intelligent. Why | |
can't we just tell C# what we want, return null if any of the | |
parameters are invalid? One way to do that is with a factory | |
-- ex3 | |
"); | |
// Person3 | |
Person3 p3 = Person3.Factory("name@email@com"); | |
string p3_email(Person3 p) { return p?.shareEmail() ?? "null"; } | |
var res3 = p3_email(p3); | |
label("res3"); | |
log(res3); | |
block(@" | |
As you can see, now we don't have to worry about calling | |
p3_email on p3 because p3 can be null (since it's a class | |
instance) and we just have to check it with p3_email. | |
At this point you might be thinking, doesn't that mean we have | |
to check for nullness all the time, verifying at every step of | |
the way when an instance can be null, when a primitive can be | |
null providing checks upon checks upon checks of null checks? | |
Yup. Because that's how C# was built. | |
The biggest problem is that any value can be null at any point, | |
if you're forced to use a plugin for Unity or an API, you are | |
forced to either read the entire source code for the plugin/API | |
to make sure there are no possible nulls, trust whoever made it | |
that they are perfect human beings who can't ever get anything | |
wrong, or check for null at every point of contact with that | |
plugin/API, much like how `p3_email()` does it. | |
The worst part, is that Person3.Factory never EVER tells us | |
that there could be a null as a return type. So How the fuck | |
are we supposed to know??? | |
One way is to use an Option<> type. Take a look at Chapter 8. | |
"); | |
} | |
public abstract class Option<T> { | |
public static implicit operator Option<T>(T value) => new Some<T>(value); | |
public static implicit operator Option<T>(None none) => new None<T>(); | |
public abstract T getOrElse(T elseValue); | |
public abstract T getOrElse(Func<T> elseFunc); | |
public abstract void getOrElse(Action elseAction); | |
public abstract TOut Match<TOut>(Func<T, TOut> someFunc, Func<TOut> noneFunc); | |
} | |
public sealed class Some<T> : Option<T> { | |
public T value { get; } | |
public Some(T value) { this.value = value; } | |
public static implicit operator T(Some<T> some) => some.value; | |
public override T getOrElse(T elseValue) => this.value; | |
public override T getOrElse(Func<T> elseValue) => this.value; | |
public override void getOrElse(Action elseAction) { } | |
public override TOut Match<TOut>(Func<T, TOut> someFunc, Func<TOut> noneFunc) => someFunc(value); | |
} | |
public sealed class None<T> : Option<T> { | |
public override T getOrElse(T elseValue) => elseValue; | |
public override T getOrElse(Func<T> elseFunc) => elseFunc(); | |
public override void getOrElse(Action elseAction) { elseAction(); } | |
public override TOut Match<TOut>(Func<T, TOut> someFunc, Func<TOut> noneFunc) => noneFunc(); | |
} | |
public sealed class None { | |
public static None value { get => new None(); } | |
public override string ToString() => "None"; | |
private None() { } | |
} | |
class Person4 { | |
string email; | |
public static Option<Person4> Factory(string email) { | |
bool valid_email = Regex.IsMatch(email, @"([A-Za-z]+[\w.]*)@([A-Za-z]+[\w.\-]*)\.[A-Za-z]"); | |
bool valid_arg1 = true; | |
bool valid_arg2 = false; | |
if (valid_arg1 && valid_arg1 && valid_arg2) { | |
return new Person4(email); | |
} else { | |
return None.value; | |
} | |
} | |
public Person4(string email) { | |
this.email = email; | |
} | |
public string shareEmail() => this.email; | |
} | |
static void Chapter_8(string title_name = "Chapter 8 - Option Type") { | |
title(title_name); | |
block(@" | |
Our final chapter, mainly cuz I don't want to write more. | |
Also, this is an advanced subject, you need to know, operator | |
definitions, implicit type conversions, generics, and abstract | |
overriding :D. | |
To understand the title let's give an example: | |
-- ex1 | |
"); | |
int div(int a, int b) { | |
return a / b; | |
} | |
// comment the try/catch if you wanna see the error | |
// log(div(12, 0)); | |
try { log(div(12, 0)); } catch { log("Error"); } | |
block(@" | |
If we try the above we are going to get a | |
`System.DivideByZeroException` | |
Because we divided by 0. That makes sense to us because we know | |
that we can't divide by 0, we understand what dividing means, | |
and so the error is expected. | |
However, what if we have a | |
`int BlackBoxFunction(int, string)` | |
And we call it and now we get an error? That would be totally | |
unexpected and now we would have to read the implementation of | |
how `BlackBoxFunction` works, which is annoying. Where's the | |
problem? | |
The problem is that `BlackBoxFunction` and `div` are both lying | |
to us, they say they return an `int` but they can sometimes | |
return an error. | |
Classes are the same since they are nullable, if the function | |
has the following signature: | |
`Person3 Factory(string email)` | |
This could mean that either it returns a `Person3` or `null`. | |
Which is infuriating, because that means that now when we use a | |
it we need to check if it's `null` every time, which ends up | |
being very annoying. And if you don't do that you'll end up | |
with an error, at the worst possible time. | |
`int` and `bool` for example are not nullable, and if you want | |
them to be nullable, you need to type `int?` or `bool?` which | |
will make it inherit the `Nullable` interface, and it has two | |
methods `HasValue` and `Value`, which are very handy, so that | |
you don't need to be comparing it to null all the time. | |
-- ex2 | |
"); | |
bool? a = null; | |
bool b = a.HasValue ? a.Value : false; | |
block(@" | |
So, it'd be really nice if we had the same functionality for | |
any type that we make. | |
I have defined some classes, feel free to take a look at them. | |
-- ex3 | |
"); | |
// Option<T> | |
// Some<T> | |
// None<T> | |
// None | |
void LaunchRocket() { /*...*/ } | |
Option<int> div1(int x, int y) { | |
if (y == 0) return None.value; | |
return x / y; | |
} | |
int val1 = div1(12, 0).getOrElse(0); | |
Option<int> val2 = div1(13, 0); | |
bool hadToElse = false; | |
int val3 = val2.getOrElse(() => { | |
hadToElse = true; | |
LaunchRocket(); | |
return -1; | |
}); | |
bool error = false; | |
val2.getOrElse(() => { error = true; }); | |
label("val1"); | |
log(val1); | |
label("val2"); | |
log(val2); | |
label("val3"); | |
log(val3); | |
label("hadToElse"); | |
log(hadToElse); | |
label("error"); | |
log(error); | |
block(@" | |
Finally, let's take a look at the same code from Chapter 7, to | |
see just how nice it can be :D | |
"); | |
Option<Person4> p4 = Person4.Factory("name@email@com"); | |
string p4_email(Option<Person4> p) => p.Match( | |
p_isValid => p_isValid.shareEmail(), | |
() => "Person does not exist" | |
); | |
var res4 = p4_email(p4); | |
label("res4"); | |
log(res4); | |
block(@" | |
As you may see, has a bit more boiler plate, but now we know | |
that p4_email may or may not be passed an actual Person, and | |
therefore we are no longer lying about whether it could be | |
there or not. | |
My implementation is extremely simple and not very robust, so I | |
don't actually recommend using it, instead use something like: | |
- https://github.com/louthy/language-ext#unity | |
It has all the function written about until now. Including | |
compose and pipe, and filter, map and reduce, which are super | |
nice. | |
There are also the Either and the Try type. Either tries to | |
pass around errors instead of possible None values, and Try is | |
kinda like try catch, but nicer. | |
And that should sum everything up; | |
More interesting reads on Monads over here (ah, yeah, the | |
Option is called a monad): | |
http://codinghelmet.com/articles/custom-implementation-of-the-option-maybe-type-in-cs | |
https://davemateer.com/2019/03/12/Functional-Programming-in-C-Sharp-Expressions-Options-Either | |
https://kwangyulseo.com/2015/01/19/step-by-step-implementation-of-option-type-in-c/ | |
https://github.com/tejacques/Option/ | |
:D | |
"); | |
} | |
static void Epilogue() { | |
block(@" | |
For more info, here's an excellent video that uses JS: | |
- https://www.youtube.com/watch?v=FYXpOjwYzcs | |
Another one using TypeScript: | |
- https://www.youtube.com/watch?v=tmVk_4oRL-Y | |
C# with application: | |
- https://www.youtube.com/watch?v=UM-3ZsHhogA | |
F#: | |
- https://www.youtube.com/watch?v=srQt1NAHYC0 | |
Production ready code: | |
- https://www.youtube.com/watch?v=v7WLC5As6g4 | |
Kinda slow, but shows inner-outer refactoring pattern: | |
- https://www.youtube.com/watch?v=s8ru33IIQzc | |
More abstract: | |
- https://www.youtube.com/watch?v=0if71HOyVjY | |
Scala: | |
- https://www.youtube.com/watch?v=m40YOZr1nxQ | |
Category theory: | |
- https://www.youtube.com/watch?v=JH_Ou17_zyU | |
Anyways this is not even the beggining, but it should be enough | |
for you to get started with FP. Remember, you don't have to use | |
it all the time, but it can make your life so much easier at | |
times. | |
"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment