Functional Python
Audience
Intermediate Python programmers who like to learn more about functional programming and its application Python.
Format
The tutorial will be hands-on. I will use JupyterLab and will start with an empty Jupyter Notebook. I will unroll the tutorial content by typing. In addition, I will distribute scripts before the tutorial to avid too lengthly typing. I will load these scripts one by one into a Notebook.
Participants will have the opportunity to type along. I am a rather slow typer. In addition, I will stop typing often to explain. This gives most participants plenty of time to follow along. The PDF handout is very comprehensive and contains most of what I type. This allows students to pick if they should fall behind.
Outline
- Functional programming basics (10 min)
- Overview programming paradigms
- Features of functional programming
- Advantages of functional programming
- Disadvantages of functional programming
- Python's functional features - overview
- Pure functions (5 min)
- Callables and functions in Python (20 min)
- Callables
- Closures
- "Currying"
- Partial functions
- Recursion
- Lambda
- Single Dispatch
- No Loops - map, filter, and reduce (10 min)
- Processing iterables with map
- Select from iterables with filter
- Reductions of iterables with reduce
- Operators as Functions (10 min)
- Arithmetic operators
- Logical operators
- Attribute access
- Lookup
- Comprehensions (15 min)
- Simple
- Nested
- Dictionary comprehensions
- Set comprehensions
- Iterators (15 min)
- Itertools
- Infinite iterators
- Iterators terminating on the shortest input sequence
- Combinatoric iterators
- Itertools
- External tools (5 min)
- More itertools
- Toolz
This session took place in track Programming & Software Engineering and was classified suitable for novice domain / intermediate python by the speaker.
Transcript (auto)
Auto-generated from the recording utilizing Open-Source AI. Speaker labels (Speaker 1, Speaker 2) reflect diarization, not identity. Timestamps refer to the recording.
Speaker 1 [00:06]
Thanks for the nice introduction. So I always say I've been a dedicated Python user since 1999. I started this version 1.52, if you remember. So I can always claim the last millennium. So I've been doing Python for a while and also teach Python for a while. And actually now my main job is teaching Python. A few questions about you. I just want to know a little bit how much experience you have programming Python. So who programs Python one year or less? Please raise your hand. very few people between one and three years inclusive okay three to five that's a big more than five so yeah a wide range of distributions so the time of use Python isn't just one indicator it's not not everything so this is a download link if you download this you will find a PDF and a few files so there are no requirements in some installing anything we just use pure Python I use Python 3.12. If you want to use 3.10, 3.11, 3.9, even, doesn't matter. There shouldn't be any difference because we don't go in all these features. So once you download everything, if you don't have the link yet, I will come back. You will get a PDF, and we quickly go through a little bit to the content. If you download, you will get this PDF about functional Python. It's like 50 pages, which is way too much for 90 minutes. So I have to cut it short little bit and I still give you a little bit the feeling of functional programming and Python. We cannot really go into depth, we cannot do exercises because the time is a bit limited. Typically I teach tutorials of PyCon US, we have three hours, then you can do some exercises, but if you want to you can stretch this material to a whole day easily, no problem. So this is what you want to do. We see here, first I give you some introduction, I will speak a a little bit and tell you a few things, and then we do interactive programming. I will program it a little bit. You are welcome to program along. I will use Jupyter Notebooks if you want to use Visual Studio Code, PyCharm, or whatever you want. It doesn't really matter. You can use any tool you like. There's no requirements for any tools. Notebooks are very good for teaching. That's why I use Notebooks. So that will be a few things about the general thing about functional programming and Python. And then we go through a few features like what's a pure function, we'll learn it doesn't exist in Python, because obviously, if it's function programming, functions are in the center of everything. We talk about call levels and functions in Python, so when you say a function, most of the time you mean a call level. Then we have a few things about map, filter, and reduce. Those basic things are basic building blocks of function programming. Actually, they're related to comprehensions. We also talk about operators as functions, and then we have iterators. You might use iterators already, which is a feature that you get put. If you want to, you can put it in the functional camp. And then we have things about other tools, like more tools, more other tools and tools that's written as I said at the end, just a few other libraries. OK. I'll start talking a little bit about a few things here. So let's scroll down here. So this is beginning the table of content. And I will just lecture for a few minutes. And then I promise not lecture anymore, but show you examples. So functional programming, it's all about functional paradigms. So you might have seen some talks at previous conferences where people say, oh, this paradigm thing doesn't matter. But still, they're around. So paradigms are ways of how to program. If you go to Wikipedia, I think you'll find more than 70 different paradigms. I don't know all of them. There's many of them. And they're just ways describing way of programming, like event-driven programming or aspect around the programming, which I gave a tutorial on. Many other ones that have nothing to do with this categorization we have here. But if you go there, you have two big camps, imperative programming and declarative programming. Most of the people program in imperative style. So if you do procedural programming, if you write functions and you call them one by one, it's not functional programming yet. It's just procedures. You call the procedures. Or if you do object-oriented programming, which is a very common way of programming, it's still imperative programming. At least there's this classification. You might find some other places where they have some other schemes. Who knows? But that's what Wikipedia says. And then there's a big camp of declarative programming. And they have functional programming, logic, and mathematical. So logic is prologue. And if somebody uses prologue, they write down some rules. And there's X union rules, somehow, something like this. I haven't really worked with it. And mathematical programming, no idea. The most famous representative of this camp is functional programming. Yeah? So it's a pretty different way of programming. It's not new. So anybody knows Lisp? So Lisp has been around since 1958. So I think predates most of us, I guess. And this has functional features. And the other contender was Fortran, which is very imperative, very close to the metal, and very fast. At this time, computers are really slow. Yeah, they're really slow. Punch card stuff like this. OK, this is the whole thing. And so even if we call it functional programming, Python is not a pure functional programming language. Anybody has experience with a functional language like Lisp, Scala, Haskell, anything? A few people? Yeah, so Haskell is a pure language. It's a pure functional language. And a lot of things that Haskell can enforce, you cannot enforce in Python. But you can still use functional style programming to hopefully make your code better. That's what the whole tutorial is about. You cannot do a strict function program. It's impossible. I will show you why it's impossible. But you can still take some of the way of doing function-style programming maybe to improve your programs. Good. So that's all about. And then I have already written a bit about the different languages that are around. Most of the languages, all the C class, Java, they're all imperative languages. Python is an imperative language per se, but has some features that you can call functional if you want. So it's not strict. There's always somebody's, okay, but this is not really functional. That's true, but you can still use the function style of programming to get the best out of both worlds. Good. Okay. Who's using it? So functional program is not nearly as used much, but like people do trading, I saw something. that do a lot of algorithmic things, telecommunications, they have concurrency, they have some function programming. And if you write some kind of pipelines, whenever you have a pipeline, you move something from the next to the next, this is getting closer to the functional style. So if you use pandas and you always use the dot, dot, dot thing, that is closer to the function style programming than to object-oriented programming, even though, of course, a data frame is a class, though it's a combination of both, but you have some function styles in there. So my most famous sentence when I teach Python is Python everything is an object. So everything is an object, and surprisingly, this everything object also makes it possible to do functional-style programming because in functional programming, everything is a function, but in Python, functions are objects. Yeah, so, and then you can do a lot of things with functions that you can do in functional programming. And in the real function program, you have pure functions, so the functions have no side effects whatsoever. This is not possible in Python. We see an example of why. The function looks very innocent. It looks very pure, but it's not. You cannot prevent this. So you need some discipline in Python to enforce this kind of style. Otherwise, it doesn't work. Immutable data structures. So in Haskell, everything is immutable. There's no way to unmute anything. So you cannot append to a list. You always have to make a new list that's one element bigger. You can do it in Python. It's very inefficient. In language like Haskell, the compiler optimizes this away. So to some degree, immutable data functions, data structures can be useful. Preserving state is a bit complicated because you don't have classes with instance and stuff. You use closure. So I will introduce quickly the concept of a closure, which exists in Python. And this can be used whenever you write a decorator, you use a closure all the time. Recursion, it's something that's like Haskell doesn't have iteration, you have recursion. The recursion is not a good thing in Python as a replacement for iteration. You can still use recursion if you have really recursive algorithms, because it can be useful, but Python has a recursion limit, and you cannot write 1 million steps deep recursions. Impossible. Like 10,000, 15,000 recursions, then Python crashes. It doesn't work. So recursion is not a good fit here, but we will see there's other things like iterators that you can use, and you don't need to recursion necessarily. Yeah. Lazy evaluation is a big thing in function languages. We don't talk much here. All this generator, iterator thing goes into the direction of doing things lazy. So the list comprehension, you use a generate expression, and you essentially write the same code, but it's lazy. It's only executed at the very end. And there's no global state, which you can have in Python. But if you really want to, you can avoid global state. And if you use things like pylint and you follow the rules, then usually you don't have global state too much, because says you use something global, and you shouldn't, for instance. The advantages, you don't have any side effects. Actually, in functional programming, they call it effects, not only side effects, effects, but side effects. And if you don't have this, then if anything goes wrong, and the function can only be this function, it can't be anywhere else. That's a good thing. You can make your programs more modular, usually. This style lends itself to this programming. And at the same time, you make it better testable. So if you write your tests along with your code, you will realize, OK, if you put everything in one thing, you can retest it. That's very easy. And this function style goes more in this direction. Very often, the focus is on algorithms. And also, if you write everything functional, then doing things in parallel can get easier. Like Haskell has parallel execution built in, Because all these restrictions you have there make it suitable for parallel computing. And a language like Erlang even has live updates. So if you have a program running, and then you can put a new version of the program into a live code, which probably you cannot do with other languages. Not recommended. Of course, there's no such thing as free lunch. There's all these disadvantages. And then I looked at Haskell first. I didn't understand anything. It looks very different. Sometimes it can look very different. it doesn't look very intuitive at the beginning because you have a lot of other experience. So this can be a problem. This is different. And also, if you have a team, you're finding good people that are used as a style might be a bit more challenging than using, getting somebody who knows Java. And it's not equally useful for all problems. So in Python, you have the choice. You can do everything object-oriented. You can do everything just procedural or functional. But you can combine. And for different problems, different styles might be better. Like if If you have a pipelining thing and you want to transform your data, then functional style programming can be good. If you need to preserve state and change something all the time, maybe it's not that good, makes it more difficult. Input and output actually are side effects and you need some special kind of care in this function language. In Python, you don't have to do this, you do input-output traditional way. Recursion, as I said, recursion is not a good fit in Python to replace loops. You always want to route a loop, and a loop is easier anyway. And Python makes loops actually pretty convenient. Immutable data structures, if you want, you can use them. But in Python, it can be a big, big performance hit if you do everything immutable. OK, now these are the features that I selected, which I put in the functional camp. And we will look at some of the features. And then hopefully you understand a little bit how things work. So pure functions, which we strictly don't have, But you can still make your intention clear. So in Python, it's always possible to break things. So I saw this example of somebody using z-types and say 1, equal, equal, 2. And then Python interpreter says 2, because you just dive in the z-types and modify the IDs of the objects. So if you want to, you can always break Python. But if you have some discipline and you have some rules, then you can still write pure functions in this case, rather than have to put it in air quotes here. We look at closures. closures is this, there's this language closure, function language. It's a function style thing to preserve state that can be interesting. We look at how functions are objects and what this brings for the function style. Decorators have a whole three-hour tutorial about decorators so we don't talk about decorators here, but they always can go in this direction because you produce functions, you have function factories. Immutable data types, you also don't do much. We have a quick look at list comprehensions and generators and iterators. And then we have some built-ins, func tools, iter tools, operators. We have a quick look at these ones. Lambda map filter, these things, and recursion. And, of course, there's a lot of libraries that extend Python, so you can use a lot of these libraries that are listed here. And there's even a language. Anybody knows Coconut? It's a functional language that you have a functional language, but it compiles to Python, but you can also use all Python libraries. Pretty interesting. So I played a little bit with it, but I haven't researched it too much in production. So there's a lot of those interesting things. If you want to go further, you can go further. But here, we focus on the main basic building blocks we have. OK, this concludes the lecture part. And I now produce new notebooks. And the first thing, you should always save a notebook and then give it a name. Pure. Pure functions. We look at pure functions. So as I said, there's no such thing as a pure function. And I do have, so if you download it and you unswip it, then you will go into, it's not there. Oh yeah, I'm there already. So I'm in the subdirectory function already, okay, good. So if you download, you'll find all these files. So some of the small files I haven't put in here, but the bigger ones, bigger examples on there. So if I have a simple function and I say add, yeah, I always start with a simple function and you return A plus B, yeah? So this is a nice function you put in A and B and return A plus B. So if I call my, and the return didn't come, return A plus B. And if I add my function, then you will get a seven. Yeah, that's nice. That's something you can do. But you can also write a function, yeah, this is my function, and you can do the function times two, times two, and I have some A, some data I say here, and I multiply it by two, and I return this one. Okay. Invalid, def, yeah, it has to be a comma, so when you're talking type, it's a big comma. So and times two, yeah, data. So and now we have our function. And I use my function and it works as expected. You can multiply different things and you get the result, yeah? But you can also multiply strings and so on, yeah? So everything works with here. But you can have a bad list. You can have a bad object in Python you don't know. So this is now a module called a bad list. You see, this is a list and now I can define this function Because this star here, this multiplication is not just for numbers, it's for anything. It can be overloaded. And in the background, it calls the Dunder MUL, multiplication, the special method. Now I can make my own object that does something that's not very nice. And now I have my global list here. And if I execute this one, so now I'm in the global name space, so I have my global list that looks like this. It's empty. doesn't it come? Global list. Global list, that's empty. Yeah. And now if I do something with my list, I just do it first. I multiply my list. I have a list. My bad list. I have to make instance of this bad list. So, I have a bad list, yeah? And now I can look at this bad list. It looks like this. And now I can multiply it times two and I put my bad list in, yeah? And I get this one back, yeah? And my global list now has this entry. And if I call my function again, yeah? I do the same thing again, and I get the same result. But if I look at my global list, it changed. So it changed state, because you see I have this thing here when I have this multiplication. And inside this multiplication, I modify some global state, which you shouldn't do in Python, but you can prevent it. So now when I write this function that looks like a pure function, I cannot guarantee, because I don't have any control what data is. And data, you assume it's a number. That's fine, because numbers are mutable. But if there are mutable objects, or if your objects actually doesn't have to be mutable, objects that have their own multiplication, then they can do whatever they want, and they can take global state. So as a programmer of a library that says, OK, I provide you something you should use in a function style, you have no control that somebody breaks your code. So they can always break it. And this would be one example that they just do some global things, and then your nice function is doing something you don't intend. That's a big problem, but the solution is don't do it. Solutions don't do it. You have to have some discipline not to change global state. And you cannot prevent it in other languages. I don't know why this is coming here. In other languages, like Haskell, it's impossible, but here it's possible, and you can still break your thing. This would be the first thing. You don't have pure functions, which is actually the building block of functional programming, which is kind of a small problem. But in practice, it's a cache. You can still work with it and it's not . So what you should not do, you should not have a side effect, a function. So you have like a, if I have my own function append, yeah, and I give my list L and then I say L append. This would be a side effect thing, and I can append something to a list. Now I have my list L, which is empty, and then I say list L is empty, and I say append, and I append, of course I append something to my list, and then my list looks different. So in Python you can do this, it's pretty common to do, but this would be a side effect and you shouldn't do side effects in functional programming. This would violate it. This would be obvious, you do it intentionally, but you shouldn't use those things with side effects. Good? Question? Before you link to the resources. JENS DOPKEYRINGER- Yeah, OK, the link again. So I'll give you a few seconds. So these things you shouldn't do. No side effects. Try to write pure functions, but you cannot guarantee. But the main thing is now everything is an object in Python, everything. So classes are objects, modules are objects, and functions are objects. And now we have objects, now we can do things because now we can treat these functions as data if you want, because you have objects. Code is data. This is very common in function languages. And now we can do a few things that really come close to this functional-style programming. Everybody has a link? So bit.ly functional minus pycon.de 2024. OK, so we have this one. So now if you look at the objects, as everything is an object, if I have two functions, function one, function one, that doesn't do anything. So always try to do the simplest example that works. It just returns a one. And we have function two. That returns a two, yeah? So they're not really needed in this way, but they're examples. And now I can have a dictionary, for instance, a function dictionary, and I can put in keys and these functions as object. So no parentheses. No parentheses, I put them in. And now I have a function dictionary where I store them. So it's just an example. So you can store functions. And of course, now I can call my function. And this one retrieves my function. So if I put a parentheses behind here, what do I get? What do I get? A 1. A 1. Yeah, so you get a 1. So you call the function. It doesn't matter if you use the name F1 So if you retrieve the object from the dictionary, this creates another reference to this function. So you can put functions anywhere. You could do the same thing with classes and with other things. And this is an important thing, because now this one brings you to the next thing. And I make a new notebook, because I promised not to make notebooks too big. And this one is about callables. So when you say function in Python, in most cases, you mean a callable. So there's not only functions. What else is callable in Python? Classes. JAN-FELIX SCHWARTZMANN- Classes are callable. Whenever you put the parentheses, then you call it. But you can also make instances callable. Yeah, this is an interesting thing. And you can also, because the only thing what you want, if have a function, if I have my function at, now I have to redefine my function at because I have a new notebook, if my great function at, yeah, then actually when I call this function, what happens when I use this parenthesis, Python is using this dunder call, yeah? It's the same numbers, yeah? So that's actually what happens. And so any object that has a And under call, it's a callable. And it doesn't have to be a function, but you can use this one pretty much in any place you would use a function, because Python doesn't look for function typically, but for callable. So you shouldn't look for the type, so don't use type. So using type in Python is rarely the right thing. Sometimes there are places, but you should use callable. Is this callable? And that's typically what you want. So in Python, most of the time, you don't care for the type too much. you care for behavior. That's a little bit of a problem with typing. Now, typing people have a lot of problems with this, because you just fix a type, and then you want the behavior. You want the callable, iterable, and those kind of things. Most, very often in Python, that's a sense of Python. And now the typing is changing more and more that you have this protocol support, where the protocol is a behavior. So that's what you want. So you want to have a callable. And now you can make callables here, and you can write a class that is callable. So if you want to, you could write the class counter, and you could make a function counter oftentimes it's called, which kind of violates a little bit the thing in functional programming that you don't want to change state. But you could do something like this, self.counter equals 0. And then you just have this call, yeah, this call. And you just have an argument. What do you say? Argument 1, argument 2, and I return. You can do whatever you like here, but return arg1 plus arg2. Yeah? And now I have my counter instance, yeah? And if I look at this one, it has a counter of 0. Now, if I call it just like my function, and now I add 3 and 4, and I look at the counter, now I have my counter. I've forgotten something important, of course. You see, when you talk, the main thing I forgot. You have to do the side effect thing, but fortunately, you can go back. Plus equal 1, plus equal 1, and now you see my counter goes up. So now this function, this callable, knows how many times it's been called. Whereas this thing is not functional programming, because then now you change the state of this thing. But still, it makes a point you have a callable, and instances can be callable. There are more callables. I think Luciano has nine callables in this book. I only have listed three. There are more, like generators. There are many different types of callables. But in general, you want to have the feature callable. And when you use this parentheses, You call something, and then you have callable. That's very important, because now most of the time, like if you have the sorted, you know sorted, the sorted function in Python, which is also a little bit functional because you can provide this key function here. And it's not the key function, it's the key callable. It says key function, but it would say key callable, because I can put my instance of the class, and that has a done the call and would work no problem, Because it just calls it. It doesn't check the type. It just does the thing. If the object is doing what you want, then you're happy. You don't care for the type too much, usually. So OK, that's one thing that you have, these callables. The next thing I want to show you are closures. So closures are what you use when you write decorators. But I still want to show you the principle, because this comes from functional programming. So you can have an outer function that takes an argument, argument one. And then I have an inner function, so you can define a function inside a function Python that has no limit. You can make it as deep as you like. You can make it as deep as you like. And then I return arc one plus arc two. And then I return my function. And now I can create a new function here, which I call i10. And I call auto with a 10. And this is a new function. And I can call this new function. And what do I get back? 15. 15. Yeah, there's only two numbers here, so a 15. So this one returns a new function. And one argument is frozen here, so to speak, the 10. And now I can produce functions. If you want, you can call the function factory. So this is called the higher-order functions. A function that returns a function is a higher-order function. This is just function terms. And now the question is, how does this function know the 10? It has to know the 10 somehow. And the 10 is hidden in what's called the closure, the under closure. So if you look at this, where's the closure? No, inner. I have to look at the inner, of course. The inner has a closure. The inner has a closure. And the closure is a tuple, the one tuple. The tuple in Python with one element has this comma parentheses here. So I can access this first element. And I can look at the cell content. And the cell content is a 10. So this is how they store the state in the closure. So that's a way to store state and function things. And in Python, it's used to function. Python mixes the styles to some degree. So you have this closure principle. And when you write a decorator, which is a bigger topic here, then you use these closures typically. A decorator is just functions that return functions, replace functions. And then you use this principle to preserve this information somehow. And then that's a concept of a closure. Good. So, one thing in Python you have that comes close to it is our partial functions, so that's why we can use the func tools, so Python has a func tool module, func tools. From func tools, import partial, yeah? partial. And this now allows you to create functions. You can do it yourself. It's not that difficult, but this makes it easier. So if I have a function and I want to do something, Like if I say print, so if I have my list, I have a list, and I say print my list, then I can actually apply this like this. I can take it apart and feed in this list as single arguments. And now I could set the separator. I don't want the separator to be a space. I want the separator to be a comma space. And now you could print out the list like this. So if I do this again and again and again, then I need to repeat the set again and again and again. But I can make a new function, print comma, by using partial. And this is functional programming. You can actually apply in Python to make sense. Now I make my own print function, and I freeze this argument. You cannot change this later. So I freeze this argument, comma, space. Now instead of using this one again, I use my lookforce. You can make the name shorter if you don't like long names. Now I can do the same thing, and then I don't have to supply the sep, comma, all the time. So this is something you can write your own function that just does it for you. But this partial now consumes one of the arguments. And if you look at this one, I'm not sure what documentation it generates. There is no, the documentation is not very nice. You only see a star, double star, keyboard args. You don't see that. But you cannot supply it to SAP anymore. It can still supply other arguments. So you can still supply the other keyword arguments from print. So if you wanted to, that's print. You can still supply the end file and flush, if you like. But this app, you cannot supply it, because now it produced a new function. This is not a big thing. It's a small thing. But very often in a functional program, you have a lot of small functions to do once, and you put them together on bigger ones. And that's why, very often, those Haskell programs are very short, because they have very short functions. And they just compose some functions somehow, and they do something. And this partial is one of the things that you can use in your everyday programming, produce a new function. So instead of either write, you can write the function and just call the other function and fix the ZEP. That's no problem. But here, the partial makes it very explicit. Only thing, the signature doesn't look too nice. So it would be a bit nicer if now you would use the signature of print minus the ZEP. But obviously, that's not the case. OK, partial is one of these things you can use. You have to be careful, because whenever you use something mutable, it can be a problem. So if I have my nice list, and now I have a function that I call show, and it's doing nothing else but printing out the list. all these just toy examples. But it's easy enough to understand, so you'll need a show function you can use print directly, but just to make my case. And now I can say show list. And I phrase my list, so I say I take my show function, and I phrase my x to my list. So of course, I have to say partial. So equals partial, and I freeze it to my thing. So now if I say show list, I can call it without the x, and it prints my list all the time. But if I now modify my list, and I say show list, then you see now it prints this list, because here you have one list and just makes a reference to the existing list. That always happens, but if this is a number, it doesn't matter because it's a 10, it's a 10, it's a 10. You can change the number, but we can change the list. So you probably don't want to have this here. So you have to be careful what you're doing when you use mutable objects. Then there can be sometimes effects that maybe not what you want. So mutable objects and functional programming don't go well together. They are very useful in Python. Lists is a powerful tool. Lists and dictionaries are mutable. You can do a lot of things. But then you have to take special care that you don't get any kind of problem here. Good. You can do recursion in Python. Function can call itself, but there's a recursion limit. And I think the standard recursion limit is 3,000 nowadays. Used to be only 1,000 or so a while ago. If you get recursion limit, then you see this is 3,000. So the function can call itself 3,000 times. The 3,000 first time, you get an exception. And that's a good thing, because recursions can get very slow. So recursions in Python, when you solve something with recursions, very slow. So don't go overboard with recursions. And it's not a tool for functional programming. That's not going to work. So you have to cancel recursions from your functional programming tool, uh, tools, unless you really have, like, a recursive structure of, like, nodes that circle, you have a few recursions, that's fine, but you cannot use it as a replacement for, for loops, that's, it's not going to work. Good. Uh, Python also has Lambda, so typically I don't teach Lambda, so I still remember 2006, there was a heated discussion to throw Lambda out of Python 3, still there, still there, so they made it, so Lambda is very limited, so you can write an anonymous function, so a function without the name, which is most of the time used for callbacks if you need to, like, for the sorting. So if you have, you sort it, and you just have a simple sorted, remember sorted, and you want to have a simple key function that's doing something for sorting, then a lambda can be fine. The problem is lambda, you cannot write a doc string. You always have to see the source code. And lambda is very limited. You can only use expressions. So in Python, using a def, it's usually the better solution because you can give this function a good name, which you can recognize later, and you can write a doc string. Even if it's short, not everybody might recognize it. So lambdas, it's a little bit, so I don't use lambdas a lot. Some people really like it. And sometimes it can be convenient, but very often when you come back to your code, then you really have to read, what the heck did I implement here? If you give the function a good name and a doc string, it's really easier to understand. So lambda, actually, it's a functional thing. But since in Python, lambda is not very powerful, yeah, it's kind of a split thing. But you can write functions that you can use here. Good. So I'm skipping a few things, because only 45 minutes already. There's a few more building blocks that are functional features in Python. And I can show you a few of them. And I make a new notebook, not to produce too many here. And the next one is a map and friends. So I called map. So Python has these things here. We will see most of them are kind of superseded by list comprehensions and generator expressions. But they are still there, and sometimes they can be useful. So if you do parallel programming, if you use multiprocessing, you might know the map thing. So the map means you can apply a function to many elements. So if I have my list, so I always have my great list here. And now I have a list of integers. So range only can use integers. In NumPy, you could make floats directly. But if I want to have floats, then I could use map. I can say list. And I map the function float. Actually, it's not a function, it's a class, but it's a callable, float to my list. And then I get back a new list. So map, this is an example also for lazy, because map doesn't return the list yet. It returns a new map object, which doesn't do anything. It just kind of freezes the prescription what to do. So it says, now I can produce floats from the list, but I haven't done anything yet. So you have a map object, and this is something we have quite frequently in functional programming that everything is lazy, that allows you to work with infinite data structures, because nothing happens yet. You can never materialize them. It wouldn't be possible. It's infinitely impossible. But this is one of the concepts. So map can be useful. this map is not that useful anymore because you have generator expressions iterators and all kind of other tools that list comprehensions which are not lazy it's ego but general expression would be what do you so instead of map what you would do you would use here if you want to produce a list you would lose a list comprehension only would say float X for X in L yeah that's that's a recommended way doing it so it's not shorter than yeah it's about the same if you convert it to a list it's about the same it's not about how many characters you type but this is a recommended way nowadays use a list comprehension for the eager version or if you want to have a lazy version use general expressions which essentially gives you the same thing if you use in the right way you can use this one and this generator could be materialized into a list and And then you have the same effect. So this map is still there. So you might see this. If you do parallel programming, you will see this map a lot. Because then you say, OK, now apply this in the same function in many processes or threads or whatever kind of theme you use. Do some parallel thing, kind of. So threads is not real for CPU-bound parallelism, but there are other kinds of parallelisms. And you, like, call subprocessors in the background, and you can use threads. And then you can also use map. And multiprocessing has a map function. We say, OK, map this function all over these processes and do it there. And then you see this connection between this function style and this parallel that fits pretty well together. And we have this mapping here. So that's one of them. The next one would be filter. So filter would need a callable. So I create a quick callable. And I say greater 5. And I just say x, and I return x is greater 5. So you need a function that gives you back true or false. And now I can say I apply this one to my list. I say list, and I filter my list, filter my list, and give in my function greater 5. And I give my list. And now I get only the ones that are greater than 5. Again, the list is here just to materialize it. Of course, the way of doing this would be x for x in L. And then you put the f, the condition at x greater than 5. This would be the way to do it. But the filter is still round. And it's a function. So the advantage of this one, or it's a callable. So say this callable. Callables are objects. You can move them around. This if statement, you cannot move around the if statement in the for loop. Statements cannot be moved around. Functions are objects, and you can move them around. So we can move things some other place. You can store them and call them later, which gets more difficult with those things, potentially. So it could be an advantage if you have some style where you need to store it, and then you know it's a callable. And that might not be possible with other things. So that probably makes it more consistent if you use this one. One that doesn't exist, so you say from functools import reduce, so that used to be in Python 2. It used to be a built-in. It moved into the functools module in Python 3. But it's still there. And the reduce is a reducing. So this one would reduce. I have to define my add again here. You see, should maybe keep the same notebook, but I have my great function add, and now I can apply this function to all elements. This reduce only works as function that takes two arguments, and now I can say reduce, and I apply my function add to all my arguments, which is essentially the same thing as sum in this case. So if you use NumPy, universal functions, they all have a reduce, all the ones that take two arguments. So NumPy has a few functional things in there, and this reduce you can put in this camp, and it can be useful. So this reduces this iterable into one. Of course, instead of add, you could use multiply, and then multiply all the numbers and so on, whatever you want to do. But this is something you can use in Python if you want. And some libraries like NumPy also supply this. This is also a tool in functional programming. And maybe it can be useful from time to time. And of course, instead, now here, maybe one small thing here, instead of going back, I forgot this one. Here, this might be a case for lambda. So if you want to replace this very simple function, you can replace it with a lambda. you say lambda. And now you have, this is an argument x. And after the colon, this is what you return. And this is now producing exactly the same. So this is equivalent to this up here. And the only thing, now you have to read the source. You always have to read the source code, what it's actually doing. If it gets more complex, it can be tedious. And here, you can give it a name greater five. Yep, it's pretty suggestible name. This is still readable because it's very short. The more complex it gets, the more difficult it is to work with lambda. But here, you can save these two lines to finding a function. But this can be reused other places. This is not reusable. This exists only here. That's gone. It doesn't have a name. So if you assign it to a name, then you can also use def, I would say. Better use def. That's a better thing. Good. So now we have a few building blocks. And along this, you see now we use a lot of functions. And if you use a lot of functions, and I implemented my add many times, but actually I don't have to. I can say import operator. In this operator module in the Python standard library has a lot of functions, and they are not per se functional, but they can be used for functions like this add function is exactly what I just implemented. So you don't have to put it there. So it's essentially, of course, you have to put numbers in. So it's essentially the plus sign as a function. And you have many of these, plus, minus, and so on. And you don't have to implement it themselves so you can use them. So when you look at functional programs, very often they have a lot of very small functions. They have an identity function. What's an identity function? Identity function returns the argument it gets. Because you need a function all over the place, and sometimes you just need a value. But it wants a function, and then you have identity function. So this is the simplest function you can ever write. You get x as an argument, you return x. And so we have a lot of small functions. This operator module goes in this direction. Now we have a lot of small functions that do something. And actually, I can look at, I have some calculator. Let's load the calculator. So I wrote a small calculator here. Calculator. So this is a small calculator. You see, I use this operator module, and then I use this function addSubMultInTrueDiff, which is plus minus times divided by, yeah, so mathematical, you know, 1 divided by 2 gives you 0.5. And I have these operations here, and I don't have to implement these functions. And then I have a very small, very simple thing that actually does an operation. It just, it always has to be one number in operator and another number, and there has to be spaces inside. has no error checking whatsoever. And it converts both numbers to a float. And I do have a calculator. And if I do this, I have this input here. And hopefully, this works. If I execute it here, and I say 1 plus 2, yeah, it gives me a 3. And since it converts everything in float, it answers in a float, not an integer. You can improve on punters, but it's very easy. But now, you see, I can now convert this plus as a string into an operation. And it's very easy. You just use a dictionary, and the dictionary calls function, and they don't have to write this function. So that's an example. You have a lot of these smaller. And there's more than just plus, minus, and divided. You also have item, getter, and so on, so operator module. So if you look at this, then you have attribute getter. So instead of writing object dot something, you can write the dot. And you have other things. So these can be useful for sorted. Sorted wants this key function. And say, I want to sort my dictionary by this key. Then you say, OK, get this key and return it in this sorted, in this key function. So that you can compose it. The sorted is a little bit functional because it takes this key function. And then you can use item getter, I think it's called. Yeah, item, item getter, yeah. You see, an item getter is a function that's doing the same thing as getting items. So let's click a sample. I have a dictionary, so a very simple dictionary. And I, yeah, it's a dictionary. And I can say, I get this item. Now I can use my operator, operator.itemGetter, and I can say put my dictionary in this one as a function. So now I have this itemGetter, and now this is a function that returns a function, and then I can use this function and self, sorry, itemGetterA. And now I have this new function, getA, and then I can use getA, and then it would be applied to the dictionary. Now it's getting this one. And now this one, you can be used to retrieve. You don't have to write it yourself. You can use this directly and sort it, and sort by, if you have a list of dictionaries, you sort by the key A. Those kind of things. And as you can see, there's very small functions, and you I can put them together to bigger functions. And this is what I call functional style programming. It's not hardcore, mathematical, functional things, but it can be pretty useful. And you also have, in the operator module, you have something like comparison, less than, less equal, and so on. A lot of these useful things. You don't have to write your own small helper functions. You can use operator module. Saves you time. Lookup, item getting, all kind of things. So I have way more examples here that you can look through. OK, we don't have much time. This is this map and all the small operator functions. And we go to the next topic, which most of you know already. So I guess we can go a bit quicker. And I mentioned already it would be comprehensions. So since Python 2.2 comprehensions, you can write comprehensions. And we had this already. Instead of the map you produce, you have a list. And then you can do something with your list. And these comprehensions are extended. So I have my list, range 2 to 11. And then I can do something with my list. I can say x plus 5, x plus 5 for x in list. So that's the replacement for the map. And we can combine map and filter, as we see, because I can put the if directly here. So we have both of them, if x greater than 3, and then I reduce the result because now I have a condition. So this is list comprehensions. So you can do lists. If you don't like lists now, you can also do set comprehensions. So instead of creating a list first and converting the list to a set, you can produce a set directly. And you can also, nowadays, quite a while, also have plus 5 already. You can create a dictionary by using this one and the user column. Now you have set comprehensions, dictionary comprehensions, disk comprehensions. The logic is the same for all of them. And you don't have to create a list first and convert it somehow. You can do it directly. And of course, all of them can have a condition. You can use many fours in a list comprehension. So only use one, but you can use two, three, four, five, six. But this gets really hard to read. So when you use a list comprehension, very often, to do this, I have to start with a loop to get it right first and then move a list comprehension. Then probably you can stay with a loop in the first place. So that's because what is the advantage of using a loop? So if you want to use exceptions, there's a try-except. you cannot do it in list comprehension. Both are exceptional statements, and those comprehensions can only use expressions. Who knows the difference of a statement expression? Yeah? The expression produces a value. Yeah, so 1 plus 1, that's correct. This is an expression which is always a value, whereas when I say OS, they say OS is not there, and I have an import statement. And now, after I do the import, it's there. Now I change my system, my notebook. This is an import statement. And the import statement doesn't return anything. That's also an import function, but this would be different. But this doesn't return anything. It just changes the state. And this import has to be online, but an expression can be used anywhere. So when you do functional programming, you typically use expressions. And therefore, you can use exceptions. Exceptions is an object-owned feature, more or less. And functions language typically don't have exceptions. So this wouldn't fit. But in Python, you very often need exceptions to do the correct programming. And in this comprehension, you can use exceptions. There's no way to do anything with exceptions there. I mean, try accept. You get an exception, but you cannot catch it directly inside the comprehension. You can do it outside. That's possible, but not inside. That might be a bit a problem. So you can do all these comprehensions, and you can nest them. And this is a way to work with Python instead of using map and filter, you cannot really replace the reduce thing yet. But map and filter can be useful from time to time if you really want to have an object that you can send somewhere and you say, OK, I'll give you this function. Please apply this function. Then maybe you can still use these. They are still there. They're not removed, but their usage is much less because we all have these comprehensions. Good. So I'm trying to go as fast as possible and still get you something here. OK, so let's look at the table of content where we are. So we have comprehensions. Yeah. Operators as functions we had, callables. I still have something to iterate us. And then we have a lot about these tools. And then I pretty much covered most of these things. Of course, I want to keep some room for questions also. So if you have questions, put them in the Slido. And then you can vote them up because, potentially, there will be more questions that I can answer timewise. So if you go to Slido and then you can vote them up, then we see which are the most interesting questions about these things. Good. OK, so we have these concepts of these comprehensions. And I want to come to the next one. And this is iterators. So I make a new notebook. And we talk about iterators. So, iteration is a very important concept in Python, and you can write an iterator. And for this one, you see, if I have it, I don't have to write it here. No, I don't have an iterator here, so I have to write myself. Yeah. So I write a class and I call this countdown, have a countdown class. And the main thing in this iterator is actually I have an init and I give it a start value. Start equals start, yeah. And then the main thing in the iterator is this done the next. So whenever you do something special in Python, you use a special method. And this next method is what we need here. And I just do something, actually I say start plus one because I want to, I will see this logic wise. And then I increment my, and I don't call it start, I call it counter, because the start is a different thing. So if you read what's in there here. And now I increment this counter. So we had this before, we increment the counter. And then I say if self.counter, I don't increment, I decrement. So otherwise it doesn't work, should count down. If self.counter is less equal 0, so the equal, the less catches, it might be negative. I erase a stop iteration exception. So this is an exception, and that's one of the few exceptions that does have the word error in the name. That's not really an error condition. It's a signal. So exception and Python are used not only for error conditions, but in the first place, they are signals. Most of the time, they're used if you have a problem and you erase them, but not necessary. In this case, it's not a problem in the first case. And then I have and under iter, which returns an iterator. Since I'm an iterator already, I return myself. So not me, but this class. So this class is I return myself. I return myself. And now I have my countdown, countdown five. And I make an instance of my class countdown. And I give it my five. And now I can iterate over it. And I say, for x in countdown 5, print x. And if I've done everything correctly, it prints the numbers from 5 to 1 and stops. It knows 0. That's the whole thing. You can iterate over it. And this iteration consumes the whole thing. You can consume the whole thing. But I can also make another one. I make another instance of my countdown. And I can use next. So whenever you iterate over something, Python is calling in next in the background. And I can call next, next, next, and produce a next element. That's what this iterator supplies. And I can also consume the whole thing by handing it into a list or set or other functions. Actually, this is not a function. It's a class. But other callables that consume this thing. And I can now consume the rest. And if I say next again, now there's nothing there anymore. And therefore, I do get a stop iteration exception. Next has, since a few Python versions, you can also say, OK, if it's not there, I want to have a default value. So if you want to prevent this exception, you can provide a default value. And now you have this thing that works as an iterator protocol. It's in many places. And you see, this one is not, I write the whole rules, what's going to happen. And I can use one element at a time. And I don't have to consume the whole thing. I can do it one after the other. So essentially, when I loop over it, what happens? If I make another instance here, it's like while true. So don't do this. It's just an example. try print next CD5 except stop iteration break. So that's pretty much fully correct. So don't do this. You can use a for loop. That's just an example of how it works. That's actually what happens conceptually in the background. It's calling next, next, next, until there's a stop operation it stops. And this is a protocol, and this protocol is used a lot. So if you use functional programming, usually you don't have, you don't use classes, you use lists and dictionaries and other primitive or built-in data structures, and then this is a protocol, most of them are iterable, and then you can make your own objects iterable and can move them in the chain because the only thing that is expected, you can iterate over it. And here you can do all kind of stuff in the class. Again, we talk about functional programming, yet I write the class, yeah, but that's the way to do it in Python, you write the class, so the class itself, of course, is not functional, but then when I use it as an iterator, it's more functional features, yeah, so that's the whole thing here, so it uses a functional thing, and you can work with it. So if you work with iterators a lot, then you can use the iterator tool in Python, and And that's a great module, import IterTools as IT, like PD for pandas, IT for IterTools. So you can make it short. And now you can work with IterTools. And IterTools has a lot of different helpers. You can write most of them yourself if you want. But again, there's a lot of the small ones that you use again and again and again. So you have a library. For instance, you have groups that have one that's a cycle. You have to be careful with this one. Now I can use next. Of course, I have to make it a cycle C, APC, cycle APC, whatever you want to call. And now I can use my next here. And I can use and can call this one next. It turns green. Next. Now I can repeat this, and then you see when it now should end actually, but it doesn't. It starts at the beginning, so it cycles around. So this can be very useful if you need something that goes around and around and around. There are some use cases for it. It's not difficult to write, but why write it when it's there? The big advantage is you don't have to document it. It's part of the standard library. If you write yourself, you have to maintain it and write the documentation. For a small function, that's not much, but usually it's better whenever you can, you what's in there unless you have big differences but if it's good enough then use this one you don't have to maintain it so that's one of those endless once so you can generate the endless structure this cycle and go around and around similarly if you want maybe for some tests yeah ever three so you can say okay IT repeat and you put industry and this one's now every time you call next. It gives you a three. Yeah? So don't convert this into a list. Why not convert into a list? Yeah, so it tries to make a list that's infinite, which doesn't work. It consumes all the memory, no matter how much you have. And then you get a memory error in Python. And it might freeze your system on the way, so it's not good. So you have to be careful with these endless structures. That's a thing in functional programming, was most of the structures are lazy, and many of them can be infinite per default, and then you should not materialize them. So there's a few interesting ones in here. One of them is chain, so you can actually chain multiple. You can chain multiple iterators together, so you can give. You see this star syntax, you can put as many as you like, and they're used as one. So you can iterate over all of them. It's like using a plus. You cannot use plus for generator objects, like using a plus between lists and then iterate over the whole list as a chain. So that can be useful. And everything can stay in iterators. Once you're in iterator land, then you should try to stay with iterators or generator expressions as long as you want, and only in the very end materialize it. This can be very memory saving. And also, maybe you go through and then you realize you have a big list of million elements. After 10 elements you have, what you want, you can stop. If you materialize everything, you could generate a lot of extra numbers. And this is the whole thing. If you work with this style, then you want to stay as long as possible with this lazy object, and only at the very end materialize if you need to. And a good thing is if you sum something, if I sum an object, So range is not an iterator, but has the same features as in terms of memory saving. So it doesn't generate intermediate lists because the sum is consuming and produces only this number, and it doesn't use any large memory. So don't produce a list first just to sum it. Try to stay in iterable land. And then if you have to reduce things like sum and others, then you can save memory. So if you do it correctly, that doesn't work for every problem. As soon as you want to sort, it doesn't work. So if you sort in Python, you have to have a list, and you have to do the memory. If you want to build in sort, there's no memory-efficient sort. You have to use a sort, and this always builds a new list of memory. There's no way around it to get anywhere. So let's look at the, to shorten this a bit, because this is a library, so libraries have a lot of things you need to read. And you can remember most of the things anyway. But to get an overview, you see here I have a table. These are the infinite ones. We have a count that keeps counting. So this is like an endless thing. It keeps count, count, count. But now we call next, but this is the next value. And you can also provide a step. The cycle we've seen, there's a repeat. And there's many more. So there's also accumulate. So if you use NumPy, universal functions, you also have accumulate functions, similar here, that would accumulate everything. So it's like the reduce, but keeps intermediate results. So it sums up, but keeps all the intermediate results, and so on. Then we have different chain versions here, change from iterable. Drop while, these names, if you look into functional programming books, you'll find these names. So drop while is a classical thing. So drop while. Group by, you have to be careful. If you know pandas group by, this is not. You have to sort first to do group by, and it's a bit different. So I think the pandas is more powerful than this, I have to say. But group by is a typical thing in functional programming. Filter false is the opposite of filter and so on. So I slice is interesting. You can slice iterators, like slice notation. You have to be careful when you slice. Whenever you do slicing, you consume this iterator partially, and you advance it. So when I slice a list, and I slice it, it gets over the same list. If you do this one, it consumes the thing. Next time you slice it, you get something different. Be careful. Take while is the opposite of drop while, and so T, you can kind of like a T. So you can copy it. And you have like two parallel iterators. You have to be careful. They should advance pretty much into the same step. Otherwise, it has to make a lot of copies in between as it does something written. SIP longest is maybe the classic. So SIP longest is something that's rather iterator here. So if you have, if I say, for x comma y in SIP ABC range 10, then the normal SIP behavior is the shortest one wins. The SIP always says the shortest one wins. So if one of these sequences is empty, you have zero steps. Then you say, from IterTools, import SIP longest. Then the story is different. Then as the names are just, not the shortest one bit, but the longest one. So this is something that you might use in your daily programs. And of course, if you don't like this none, you can supply the start value here. And you see this is called start? No, no, no, it's not. Defaults, just a value. I think it's no name. So no, it's zip longest. It's called default value, yeah, default value. It's called fill value. It's called fill value, fill value. And it doesn't comment. It's fill value without an underscore, whereas in Pandas it's with an underscore, just to make it more fun. So OK, they can fill it here. If you have more than two, you can only use one fill. You cannot say for this column, this fill, then the other one. But this is something you might use, sip longest. And it's from functional there. Again, the sip object is, if you do sip, you get back an iterable object, a special object, which has a next. So if I sip something together, then again, we have this functional kind of thing here. now I can say next and we have the same behavior as before and you can get the next element and if zip is lazy it doesn't produce anything so using zip same goes for enumerate zip longest then you can always stay lazy and only at the very end consume everything and produce values and for certain types of tasks this can be very useful because you can save memory and also you don't have to allocate so much memory it also takes time if you do this a lot So, there can be some improvements here. Good. This is IterTools. This is part of the standard library. And if you go to documentation of IterTools, you'll find a lot of recipes, but just written in text. And these recipes are implemented in what's called more IterTools. And they have, I don't know, 27 properties, is that correct? So there's a lot of recipes for typical things you want to do in functional style. So this would be something like you can spend a day doing the examples here. But you see there's a lot of these things like consume an iterator, just consume all the values for some reason, maybe you want it. And many of these different recipes that are written in the documentation are spelled out here. And then there's more iter tools, new recipes, and you have all this kind of stuff here. And some of them you might recognize. Pandas has some similar, like windowing, something similar. So there are some parallels here. Matthew Rockling is a functional programmer, and he's also working with things like this dust. Everything goes in the same direction, has some of the flavors here. And you can use all these kind of small things. Again, very small functions doing small things. You combine them to bigger functions. That's typically the function style of programming. So as I said, we skip the exercises because we have not so much time. And if this is not enough, you can also use other tools. There's quite a few of them. The tools, as I said, here is probably the one that can do the most. So you need to install it. And then you can work with this library. And this has three things. It has itertools. It's a spelling of functools and dictools. And they can do a few things. The scoring, I skipped over it, is also like creating new functions here. So you take a function that takes multiple arguments and produce many functions that have one argument. That's just like Haskell works. That's the stuff. So they have a lot of stuff in here that goes in this direction. And you can use it. And you can introduce, step by step, more functional style features in your program. And these are a lot of useful functions you would sooner or later maybe write yourself. And you don't have to, because they provide it already. I have a lot of examples here. Function composition, so you have multiple functions and you put them together into one function. That's something very typical because it's possible functional objects, you make a new function out of consistent functions. And instead of do this, you can make a new function. So you use a compose and this compose composes. So instead of calling these functions in this nested style, you get a new function that is doing this all for you. Everything is a function. You have functions, return functions. Python's no problem. And then you put them together to pick up functions. That's typically the function style. And that's something you can put in your thing, in your normal Python program. Good. I think we have how many minutes left? Five. So probably, I will still have 10. OK, good. So I was going through here. But still, maybe we open it for questions. If you want to, I can go to some of them in more detail, because I skipped through quite a few, and I'm not sure who's interested in what the most. So if something is unclear, you want more details, or you have further questions, maybe we stop these questions now, and then I can show a few more of the topics. It would be easier. Yeah? Yeah, you start with the questions already, so then. OK, so again, you can go to Slido and vote on the questions that you find the most interesting. Right now at the top, we have someone asking you, John asking you to give an example where functional programming is better than an equivalent solution using maybe object-oriented programming. That depends very much on the application. So usually if you have some kind of pipelining, that's a typical thing. So you have data and you have different steps, you want to have one step after the other, then functional programming might be better. And if you don't need to have state, so if whenever you say I need a state, I have like a simulation and I need to modify a state, then functional programming gets more difficult. So now you have time steps and something changing over time, then you still can do it in functional programming that maybe you have an object that keeps all this state and evolves over time might be better. But when you have data and you want to process data, and you say now I have this step, and now I have this step, and I have this step, and I go through once, then functional programming can be nice because I just get this data and return new data, get this data, return new data, and I don't need any state because I just have something and produce something new, whereas I have something that has to stay over time. Like GUI programming, I think object-oriented programming is good because you have a GUI that has a lot of state and has to react to it. If you do it in a functional style, you can do it in Haskell, you have everything, but it doesn't feel that natural to me. So a GUI program would be more state-wise. If you have data, do you want to process, then functional. Of course, in your program, when you click on a button, it's doing some computation, then you can still do functional, but the GUI part, I wouldn't do functional. Yeah. Someone now is asking if there's, this was asked way earlier than this slide, someone asking for a good solution for chaining methods in a functional way, because we don't have built-in syntax in Python. Yeah, so this tools compose, yeah, so you can roll your own, but just use tools. The problem is you have a dependency, yeah, but this is not a big dependency, I think it's pure Python, and then this one is a composed one, and if you want, you can look at a source code. So you can see how they do this. So let's have a look. So I haven't installed tools. Sorry. So I have to install tools first. And then we can look at the source code and you see how it is. And it's pretty involved. So if you want to do this yourself, you wouldn't write yourself, but use the tools you can do function composition, yeah. All right, now someone is asking you to expand on why the dunder iter, when you defined your custom iterator, why the dunder iter returns self. Yeah, so you have, whenever you want, if I have a list, I have my create list, yeah, I don't have my create list. If I have my create list, I can turn my list into an iterator by calling the built-in function iter. So the built-in function iter is hardwired to call the dunder iter we just implemented, and I get back an iterator. So the list data type implements the dancer iter. Hopefully it does. And this is what's happening. So this iter calls this iter. So if you do this, you can convert this object in iterator. Since my iterator is already an iterator, I just return myself because I'm done already. So you can convert the list in iterator and many other sequences in Python, and you can do this for your own objects. So if you have a sequence object, a sequence like object, you can say, okay, now you can use call iter. And now I work like iterator, I can iterate over myself. But you are responsible to do something useful. Do you want to keep going? Yeah. Excellent. So someone says, I was surprised to see no mention of monads in many functional friendly languages. They're quite important to ergonomics. Do they feature in functional Python? I don't think so. I think monads, to my understanding, is because function languages are free of side effects and input-output is a side effect. And you have to have your problem, you have a very nice, mathematically clean side effect language and you have input-output which is a side effect, and you need something to get it in and use monads. And since in Python you don't have this problem, you don't need them, because they're a pretty complex thing. And those function languages needed to get these side effect features in their way of working in a Python, you don't need to just use the normal way. That's my understanding. Somebody has some other explanation, I'm open to it, but that's my understanding. Why is functional programming used in trading? Of course, they are very algorithmic, and they, there's two things. Probably it's useful for how they do it, and also there's a lot of functional languages have a big mathematical background. Like Haskell has a lot of papers published with mathematical things, so there's a big overlap over the people that are very mathematical that are good for trading, and that's maybe one thing, but also typically this function style is good to express algorithmic. You have this step, this step, this step, and put it into each other, and you could do pretty fancy algorithms in very few lines of code. That's probably where trading comes in. Someone asks, someone says, in languages like Scala, type safety is a game changer. So how well does type hinting work with functional Python? I don't know if there's any, of course, functional Python is just we apply some concepts and I don't think there's any difference between object-oriented Python and functional Python in terms of type hints. And usually, as far as I see, the type hinting goes more towards what I mentioned, the protocols. So not necessarily strictly the type per se, but rather the behavior. This object or this function should take this and produce this, and this can be potentially objects with different types, but they have to have a certain behavior very often. I think that's a big advantage of Python. You have this because you can write a function that doesn't need to know what types it takes and still does something useful. And as long as the objects come in, can do something. They don't have to be something. They have to be able to do something in a certain way. And that's not really typing, because typing really goes for what they are, not what they can do. But if you can put this what you can do into typing, then this would be pretty useful in Python. So now there's a philosophical question. Is Excel functional programming? This got some votes, so that's why. GERHARD HOFFSTAEDTERMACHER- Excel? Yes. GERHARD HOFFSTAEDTERMACHER- I don't know. I use Excel formulas, but it didn't feel like functional programming to me. I have no idea if anybody has some inside there. All right, so now someone is asking for your opinion on using a monad-like result or either for error handling. Would you find that too un-Pythonic? JAN-FELIX SCHWARTZMANN- Yeah, but now we have exceptions, and you have other way error handling that would be a bit problematic. Because anything you use gives you exceptions. You have to handle the exceptions anyway, and then you have another way of handling exceptions, and you have two ways. So that's to my feelings, my opinion. So if you're fine with it and it works for you, that's totally fine. But I would say you have to stay with exceptions. Because you want to use other libraries, and they give you an exception. You have to handle these exceptions anyway. Then you probably better also raise your own exception to stay in them. What's the difference between iterators and generators? So generators can do more. The usage is the same. So you have the three usage. You can use next for single step. You can iterate over it. or you consume it with list or so. But generators can do more. So I have a section about generators. So generators you can extend to a subroutine. So generators come in two flavors. You have generator expression, we handled here. And you do have generator functions. So we haven't talked about generator functions yet. So a generator function is a function with yield. And then this one you can turn into a coroutine. So iterators can produce values. generator function can produce values. But generators can also take values. And this you can convert into coroutine, which is a whole different new topic. So generators are very similar. There's a lot of overlap. But generators can do more. And the main thing they can do more is a coroutine. So now we finally reached the question of mine. So generator expressions are better for when you have arbitrary expressions. But if you have just a callable, say, F, isn't it leaner to just write map, f, iterable, instead of the? JAN-FELIX SCHWARTZMANN- Yeah. If you only have a callable, then map might be still there. The recommendation is still use a general expression. But map, if you have a function already and you don't have to write it extra, then map is still there. It's not forbidden. And it's just more a question of style. If you say, I always use general expressions. I don't use map. That's a style. But if you want, you can also use map. Someone is asking you what currying is. So currying is, in Haskell, strictly a function is, you say, y equals f of x. So a function only has one x. But now we have functions with multiple arguments. So currying is actually taking a function with multiple arguments and turning it into a sequence of functions that take only one argument. So that's what Haskell is doing all the time. Haskell, all the functions have one argument, but you use it as multiples, and then you can take it apart and have this very clean. then a function is always the same, there's one argument, and you have this currying. So this partial application is one, kind of goes in this direction. All right, so maybe if you could expand a little bit on the partial, because someone also asks the difference between currying and partials. So maybe make it a bit more clear. Yeah, so partial, it's a simplified way. It's in Python. It's part of it, and you produce a new function where you freeze only one argument. By currying, you have produced all this chain of functions. And now you have, you can freeze as many arguments. So if you say partial, you can freeze multiple arguments, not only one. So in my example, I froze, from functools, import partial. Yeah, so you can also freeze multiples if you want, but here it's only one. Yeah, so you see, you have this, you can freeze positional arguments. So this starts positional or keyword arguments. And you can have as many as you want, but use a new function like this one. Where is this useful? When you have pandas and you say read CSV, you get 54 things. And they can make a new function, read my CSV, and you can freeze this, whatever you want. Use a comma, use a semicolon, use those kind of things. You can make your own read CSV function for your special CSV. And you don't have to carry all those 54 potential parameters with you. Just freeze the ones you need. That would be pretty useful. Now the final question for now. Someone asks if there are cases where iter tools.chain and next could be beneficial to replace for loops. The answer is it depends. It depends what kind of problem you have. So if you have a statement in the for loop, then it doesn't work. So if you have only expressions, probably you could. But this very depends on the algorithm, I think. So what you're actually doing in the loop. So a place in for loop is very general. In general, probably you could, but if you do something specific, then maybe it's possible, but very cumbersome. So I don't know. It depends on what you're doing. So we're out of Slido questions. We still have three minutes. So if someone wants to ask a question, I can give you the microphone. Yeah. Question commence. Otherwise, do you want to show us a last party trick? A party trick with a juggler or something, no. Okay, in that case, let's thank Mike with a warm round of applause. Have a stick around.