 Okay, the first thing that I want to mention is that this is not going to be a more serious talk. Usually I have more specific topics that I want to talk on. This one is kind of like a little bit of an overview. It's not going to introduce you to functional programming, but it is going to introduce some concepts that might be interesting enough for you to actually start learning functional programming. So, usually before I start, I first go through a couple of disclaimers. The first disclaimer is my favorite quote from Dilavala. You should make your quote readable because the people who are going to work on your project might not be able to live, and they're probably psychopaths. And since you're in KD community, it's not most likely they are psychopaths because we all are. We used to be all are. The second disclaimer is that I tend to omit some stuff from the slides because it makes the slides too big to actually write the SDD column, column, news, contracts, etc. So, this is not production. It's very cold. Most people are really just for representation purposes to demonstrate the way. So, there are a lot of things that we can learn from functional programming and apply it in Sikos Plus, obviously. And usually when you ask somebody what is a functional programming, they tend to give you their own definitions. The problem is that if you ask two functional developers what is functional programming, you're going to get three different answers. And from my point of view, my answer is just the first part. How are other functions? So, functions that can accept other functions' arguments or functions that return not values, but another function. Apart from that, you have purity. I guess we all know what purity is. Any beautiful state. Okay, so purity if you've never changed anything. So, you don't try to database it on blah, blah, blah. But, those are all mathematical terms that I don't really enjoy teaching. And since I have a long half an hour, I'm definitely going to skip it. The reason why I got into functional programming is an evil I would. A long, long time ago I had to write software for clients for Windows and obviously I did with Linux. And I was a kid. I didn't know that C++ can be just compiled. Maybe it wasn't possible for them, but I had no clue. So, I had to use a really strange language that I'm not even going to mention in the name. And at some point, that strange and evil language got a huge new version called 5.0. And a huge, it was a really huge release. So, you understand your things like for loops that do read, but range for does. Then you get triad resource because obviously they don't know about these structures and other stuff. And the problem that they had with that new release was that most of these things didn't really mean to be the compiler itself. So, they needed to create a new version of the language, a new version of the standard to implement a compiler for something that in functional programming in languages could be done just in the library level. In C++, we had higher order functions in some sense. We didn't have Linux, but we had it since C++ was initially standardised. Since C++ 98, we had the algorithms. All the algorithms, most of the algorithms, accept functions, predicates or something. And some of them even return new functions like SDD bind. Or as it was called before, C++ 11 bind press, bind seconds, or TR1 code bind bits and similar things. And instead of the range for, we could have used just for each or boost for each. And it just implemented the same thing as the evil language did. But on the library level. So we got a single chance with a compiler. And that's why I started investigating functional programming. Because things like these, I love to extend the language that I'm working on. And obviously we had functional objects. We all know that functional object is a class with a call operator. It can be called as if it was a function. So if it crops like a duck and works like a duck, it's a duck. If you can plot something like a fun in the same way that you call a function, it's a function. So the first level in the evolution of a functional programmer is view views. So just use the tools that you're given in a completely unlawful and evil way. So the first thing that I started playing around with is the lazy evaluation. So C++ by default doesn't have lazy evaluation. The idea is, for example, just imagine you have a value that is really expensive to calculate. Like the answer to the meaning of life and everything. And you don't always need the answer. In some branches you will need it, in some branches you don't. And you can create a chain of if-else statements. You can create a function or something else. It would look ugly. Instead, you can just extend the language to support a new keyword called lazy. Open brackets, close brackets, and you just define how the value should be calculated if it's needed to be calculated. And this is completely valid in C++. This compiles and works, but how? So the first step is to create obviously a function object. It's called lazyVal. As an implementation it is going to get the function object as the template parameter and it is going to store that function object as a member. And then, okay, we don't care about new taxes. We just need to define a cast operator to the result type of the function that we got. So if you define the lazyVal and pass it a function, if you extract the return type of that function, and it will create a casting operator to this type. As soon as you want to get that value, that value will be calculated cash inside the lazyVal object. And since, okay, before C++ 17, every time we had the template class, we needed to create a main blah, blah, blah class function because the template argument reduction didn't work for classes. Fortunately, this is fixed in C++ 17, but we could have created a lazyVal function and we could call it like this. But this is still quite valid. The first slide that I showed was a keyword called lazy. And when we want to define new keywords in the language, what do we use? Just like Qt does. Everything in Qt is a macro. So here we can define a really, really simple macro. So a structure called makeLazyValAlper that will just define the operator minds. Does anybody have problems with this code? This is really evil. So in the previous slide we had makeLazyVal and we had too many brackets, and we hate brackets. How can we remove a single parenthesis from a function call by replacing that function with an operator? Because when you call an operator, you don't actually need to open bracket to close bracket, right? So we're going to replace a function with operator minus. Why minus? Because, for some reason, operator minus is his favorite. The operator is small. I decided to call it sir. So what is the idea? We have the lazy object and we have the operator minus. So whatever we pass to the operator minus will be the function of the lazy calculation, right? And we just need to create a macro that expands the minus and to a lambda f. And after that we can normally just write lazy. What we need to do is to create the helper instance. Then it will write minus. Create lambda head and everything that goes out where the system also goes out. And this is the abuse part. And I guess it's quite obvious why it gives use. You're using the language for something that is obviously not initially meant to be useful. But fortunately we have the evil macros and we have the operator overlooking to abuse it as much as we want. And apart from the fact that this is really an evil thing to implement in this way, it's really useful. Just like for each input was really useful. Again, a library feature. So the next step was, okay, so I can't improve my normal imperative code, but it's still not functional programming. Okay, I have lazy, but that's just one single part of Haskell. And most of functional programming I mean these aren't even lazy, so it's not really functional programming. The main benefit from functional programming is that people who love FB claim that you can write much shorter code than normal imperative developers do. So in 86, this is one of my favorite examples, a doubt mode was asked to write a program that lists the frequencies of words and writes out the most frequent words in some text. And the guy wrote Haskell solution 10 pages long. It was extremely well-documented, beautiful code with no data structures that he created just for that reason. And after that, the ACM journal used to ask somebody from the audience, again some really famous computer scientist to give a critique of Donald Trump's code. And obviously, when you get to critique Donald Trump's code you need to really, really have this try to do it. And Doug McCleary just sent a response with six lines of Unix-Schelzburg. So, 10 pages of beautifully documented Haskell code versus six lines of Unix-Schelzburg. Obviously, this wasn't as efficient as Donald Trump's code, but just look at it. It fits in one slide and it's almost readable if you know Unix-Schelzburg. And at the point of functional programming, Unix-Schelzburg and the pipes are really, really a functional concept. You have input, then you define a series of transformations which you want to apply to that input and you get the result. So, apply a function, apply a function, apply a function, apply a function, you get the result. Not taking about four loops, where should I store the variables or anything else. In C++, this is obviously not going to be as short as this example. But one of the things that is becoming more popular is the concept of ranges, who has heard of ranges. So, the idea is there is a huge problem with standard algorithms. And the problem is that all of the algorithms receive iterator pairs, return one iterator, which is not really composable. Or, to be honest, they're composable in a little bit different way than what we would want. So, for example, if you wanted to filter out the collection and then sort the remaining items, you really need to create a temporary vector to put the filtered items. Then you're going to need to sort it, then you're going to need to return it. And that's not how functional programming works. You should never be forced to create a temporary variable if you don't need it. You don't need to force the compiler and explain, okay, I need a chunk of memory. So, the ranges should solve the problem with algorithms by just essentially joining two iterators into a single entity called the range. This is a little bit of a lie, but trust me. So, the idea is that if you have a range, you can pass it through a transformation filter. It will return the range. Then you can pass it to something else. Then you can pass it to something else. You don't need always to keep the pairs of iterators. And how would you deal with the problem that don't have? You have a text. It's a range of characters. So, it's some data. What can you do with it? You can replace all the special characters, commas, spaces, and everything else with better chance to denote the end of the word. And you can do it. We all know the standard algorithm will transform. The algorithm on a radius does essentially the same, but a little bit lazily and just provides a view. It doesn't actually transform anything. It's just a view over the original data. When you traverse through the view, it will calculate each item we need. The next step, because we want to count words, we don't want to be dealing with uppercase, lowercase. So, let's transform everything to lowercase. Again, this doesn't do anything. It's just creating a new view over the old view over the data, something that I really like. Anybody who wants to have a hicker and likes stacks of transformations will like it. The task maker is worse. The next step, we cleaned out the data, so now we need to answer the words. We can just split the original collection over the better chance. And we get a range of words. Again, still no transformation happens. This is, again, just a view. You still have the original data structure, the original collection, and a couple of view defines all of it. After that, we want to sort it, group all the same words. After sorting, grouping, just transform, keep the word and it's count. Keep the word and it's count. Then again, sort so that we sort by frequencies. Reverse because sort will sort from 1 to maximum instead of maximum 1. And then take as many results as we want. Most of these transformations are just views. So, if you don't actually evaluate anything from them, they will never execute any code. The only different transformation in sorting, because if you want to have a sorting collection, you actually have to access all the elements in the check, which is the minimum and then the linear sorting. So, if the main library wasn't a little bit misdesigned, this code would compile. And this would be the equivalent of 10 pages in Pascal. So, to recap, we have the data. We clean out the data. We separate all the words. Sort the words. Then group by. Group all the same words into a range of ranges. And then count each of the words how many times it appears. Now, this is less efficient than it can be. We could use a hash map for group by and count, but this is much more real. And this is the solution that you will see if you're really showing in Pascal. With quite similar syntax. And how much time? I'm really fast. So, the next step, obviously when you start shortening out your code, then you'll hear about this thing called purity or immutability. And then you try to think of, okay, if I cannot change a single value in my program, how can I write anything useful? And it's something that is really hard to record head around the first time that you see it. And I'm probably not going to help you, but maybe it will be interesting enough for you to investigate together. So, if you... Yeah, I'm going to talk about financial transparency. What's the problem with problem state? There is a real nice quote by John Carmichael, he said that it's really difficult for us to think of all the states that our program can be in. And it's especially difficult when we have threading. So, the fact that we have a mutable state, which can be invalid in some cases, which we need to control but unlocks, it's a little bit of a problem. If we don't want to care about letting immutaxes, we have to have immutable data. If we cannot change data, we don't care if 10 different people are accessing it at the same time. So, just a simple function like this one has a lot of side effects. So, when the user clicks on something, what happens visible, become visible? After that, the data needs to be loaded then we are connecting to the database to be able to show it in the form. And then when the user clicks something or changes something in the form, it needs to connect back to the database. And this has become a little bit over complex with the arrows for a really, really, really simple example. So, the idea of functional programming is that you should never access data from somewhere. That you should actually, if you want something done, you should ask the person who has the data to do it for you. And the first part is that this is not only a functional thing. This is written in one of the most famous object-oriented books called Objecting by Unholy. So, even object-oriented components think that we shouldn't mutate data but pass it on to somebody who actually does the work. And, sorry, I lied. This is from somewhere, but I don't talk from there. This is the thing that is from the book. So, David Vest calls all of us who write follow-ups and the usual programs to be procedurally able to create procedural developers. The fact that we are using classes and everything, we are mostly using classes as a data container with some validation. We still are not thinking in the same way that object-oriented world should process the thing. For example, if we wanted to create a class for the dog, we would first create yet what is the height of a dog. We would create a setter, what is the height of the dog. Then we would create what is the color, set what is the color and similar stuff. If you have a real dog, can you tell it, okay, your height is now 12 centimeters. Your height is now zero. Your height is now, I don't know, two meters. No, you can't change something that is incorporated into the dog. You can feed it. You can feed it. Okay, it will grow out, but you can't tell it, okay, and now your color is right. Essentially, the objects, if you want object-thinking, you shouldn't design them as simple data containers. And that's something that really goes well with functional programming, and all these words are actually from object-oriented people, not from FB. So, besides that, object-thinking will lead to object-invitability. So, even object-invitability development will lead at some point to invitability, but the problem is how? This is the way we usually think about the world. If you want to change something, we change the world that we are in. The alternative where, if you want to declare the world immutable, you cannot change this instance of the world. And this is something that has been covered in SF and in ancient Greek philosophy. Every action that we take doesn't change the current world, but creates a new parallel. So, the idea, okay, flash. Whenever you want to change something, you shouldn't change this world, but you should create a new world with that change applied. In that way, if somebody still has, if somebody still is in the old world, it can continue living in the old world. Nothing can just get changed. No mutex is, no data, mutual data sharing, not nothing. If you create a new mutable variant, it will again never be changed after that. Everybody who uses that one will always see the persistent data. Okay? So, you can imagine something like a git. All the previous regions in git are read only, let's say. And the new revision that you create is the new world. If somebody still uses the old master, nothing changes. Okay? The problem is that creating new worlds is expensive. If you're always copying the data, copying the whole world just to change one little thing, it is going to be really inefficient if you are using the normal data structures. The first thing that you can do is just to disallow copying and force the compiler to force you just to use moves. It will be legal, because when you move from an object, if you're using it after that, it's undefined behavior. So again, there will be no data sharing for the mutable state. But again, this is just a syntax. How can you forbid somebody from copying the object and always having to write, move when you want to change something? But again, this is not the point. This is something that would force your compiler to essentially force you to think about changing the role, but it's not what is usually done in FB. In FB, instead of using normal data collections, you want to use persistent data collections. So collections that are really extremely cheap to copy extremely cheap to copy with a notification. And one of the, I guess, most famous programs that does it is Photoshop. Vice Photoshop in my head is that at some point the old about said that he doesn't see any point in functional programming because how can I do anything if I can't change a single value in my program? And he's working on Twitter, the direction back to Photoshop. And that's the reason why I like to mention Photoshop as the example because a similar program just done in a completely different way. So how do persistent data structures work? First we will start with the list which is the worst data structure that you can imagine, but it's the simplest one to explain. If you have a list and you promise never to change this instance of a list, what is a list when you prepare the item to it? You get the old list. You get a pointer to just a new element which points to the old list. So the copy is extremely cheap. The program still has the old value. The program still has the new value. Sure, a lot of you have done this thing, but not a few have done it. Yes, we have come to Appendix. If you want to remove an element, again, you don't need to do anything you're creating only to just point into the second element in the original one. Again, the data is being shared really, really efficient. Appendix is the problem. You cannot just append a single item because it will change all the previous lists and you promise never to change any of them. So instead, if you want to append to a list, we will need to actually copy all the data. And this is one of the reasons my lists are really, really bad. Obviously, the first is the cache localization. Lists are all over the memory, so you're going to get cache validation just by traversing through them. And the problem is that most of the useful functions for a list are just totally inefficient. Just repending and removing from the stack are efficient. Everything else is around all of them. So a lot of functional programming languages are based on lists and obviously, they are a little bit slower because of it. Some of them have started investigating novel data structures. And one of the data structures that I'm going to talk about here is something called bitmap vector free. The idea is that we want to create a collection that has the same optimizations, same complexity for all the stuff like STD vector. And it's not enough. We also want it to be really, really cache friendly. So what can we do? Obviously, cache is not infinite. Let's say cache can collect 32 items of some type. So the idea is if we have a few items in our vector, we are just going to have a bucket with all those items. It's cache friendly, right? It's a bucket in one place in memory. We can iterate through it. We can add an element to it. We can remove an element from it. The problem obviously comes when you fill a bucket and you want to create another one. You will create a new bucket again. When you access the first element, the whole bucket will go into the cache. So it's still cache friendly, even if you have two buckets. But now if you have two buckets, we need to be able to actually keep track of it. So we are going to create another level on top of it which points to the buckets. Again, the level above will also be a bucketed collection. So it's not an infinite collection in the top level as well. Again, it will have its own cache friendlyness because it's a single application in memory. Then if you just keep adding more, you're just going to get higher levels and higher levels of destruction. Now in this slide, all buckets here are the size of four which is definitely not the size that you would use in production. Usually you have the size of 32 which makes the tallest tree will be around six or seven levels. And a lot of scientists consider this to be not only effectively O of something, because it's a constant. You never go above seven. Seven times O of something is just O of something. So what is the expense of appending an item to this collection? So the number of layers and O of O of 1 which is in total O of 1. Appending to this collection is the same as appending to the vector. Now the question is we want to have efficient indexing as well because vectors have efficient indexing. How can we index all the items in the collection? Because the buckets have the size that is power of two. We can use the bitmap representation of the index as the key, which item in which level we should be. So for example, if we have the number for D which is 001110 and we have three layers of our structure. We can chunk the bitmap representation. If we have 00, it will be the index in the first in the top level. Then we have 11, which is the index in the element that we found by going to the first layer. The third element then going to the last one, the index is 10 and we have the access to the element with index 414. Again, what is the complexity? The complexity is the number of levels which is never larger than 7 times O of 1, which is again O of 1. The next step is so far we have considered only mutating the original structure. We didn't talk about how to make them persistent, so all the previous versions should stay alive and we are still using them. If you look at this picture and we have Appended P, what are the nodes that have changed in the original tree compared to the second one? Two blocks have changed. Two blocks, just the path to the item that we Appended. In the last one as well, even if we created a new root, the only items that have changed are the nodes on the path or the item that we have inserted. Which means that all the other items can be shaped. So every time that you append something, you just need to create a new path. Again, what is the complexity of that? O of 1 because the path is bound by a constant. And it's similar for appending, prepending, and... Well, prepending is not. What is the complexity of prepending? It's not shown in the picture. It's the same, isn't it? It doesn't matter if it's appending or prepending. Unfortunately, it does matter because if you want to prepend something, you're going to prepend a whole body and then you'll screw up all the indexes for 32 or more. There are optimizations which do fix that. But in the initial state, if you want to insert an element to the front, you'll have the same complexity like that as the director. So it will be all of that. You need to create a new one with all the data moved. Couldn't you... The modules on the front? Yeah, there are different approaches to creating efficient insertions in the middle to the top for concatenations and everything else, which I'm not going to cover, but essentially the structure is strong, is completely wound by one as complexity is conserved as the director. If you want to have efficient concatenation, you can either add the offset for the first element or something that is called a relaxed bitmap vector tree, which is an evil structure that's really cool. Instead of having a bucket containing just the elements, the first element can be a special one that does something special. But that's the more that I'm going to say at this time. Essentially the first item should point to indexes, starting indexes of all the bucket child elements. And that's something that currently, so scull and closure use this thing with the start offset. The scull should probably move to the relaxed version in a couple of part here. I'm going to skip through all of this. So if you compare this to a vector, the indexes of unjust vector appending is often just like the vector in normal case. Updating values since we have the direct line to any index we can efficiently update. So it's also often even in the immutable version. Pre-pending concatenation and inserting in the middle is obviously often if we don't add the optimization. Obviously for more information about functional data structure, one of the best. It's a really tolerable but it's still deep work. For this is written by Kiyosaka Saki. It was his PhD thesis at some point and he expanded to a proper. So let's just return to this for a bit. Most of the data is localized. So it's test friendly. It's a vector-like structure that does the same thing as a vector but has a really efficient copying. Coping with mutation. So it's even better than the original vector. This was a little bit polite but essentially the problem with it is that even if it's all of one, it's seven times of one. So you're going to get a slower access but a constantly slower access. One library that implements this in C++ I think even the relaxed version is called Emer. It's written in C++, it's recently published I think a couple of months ago. And the last time that I did a benchmark against the GCC's PhD vector for the same amount of data, a thousand or something elements, the bugs were really, really, really close. So it wasn't... It's implemented in a really nice way and here's a couple of optimizations that I didn't manage to copy. And that's it. The last slide is obviously shameless self-promotion and thanks to all the KVD for blue systems and a few other professors.