How to write multi-paradigm code
Mistler, Elias
Python is a powerful multi-paradigm language which combines elements of object-orientation and functional programming. Both concepts can be really powerful if used right. But what if you use them together? It can be pragmatic and very efficient, but things can also get messy really quickly. This talk explores peaceful co-existence of oo-classes and pure functions in the same code base. The focus is on identifying the right tool for the right job and bringing together the best of both. The main topics are: * Code Structure * Data Structures * State Handling * Multiple implementations Prerequisites: There are no formal prerequisites for this course, although it is recommended that participants have a strong background in Python and its code structuring mechanisms, as well as a deep understanding of at least one of the paradigms of OOP and FP.
 Okay, so now we have 10 o'clock and we can start with the next session. We have Elias Mislas, who is going to join us and give a talk about how to write multi-paradigm code without making a mess. Elias is a principal machine learning engineer from Previz, I hope I pronounced that correctly. He is doing a lot of machine learning and he is going to talk about using both object orientation and functional programming together and how to make those two play nicely. So let's head on to Elias. Elias, can you please start sharing your screen? Of course, of course. The second second, right, right. Yeah, yeah. Excellent. Okay, well, thank you, Mark, for the introduction and thank you, everyone, for joining. What is now the first talk here? So as Mark already introduced this about multi-paradigm programming in Python, I've got an introduction here. It's basically already said, Previz, the company I work with is an invoicing finance company and we use machine learning on large corporate data sets to predict whether invoices will be paid in the future and then to finance invoices and improve the cash flow of small and medium enterprises. I myself am a machine or yeah, machine learning engineer principal. And my main responsibility is sort of integrating our machine learning algorithms into our invoice processing platform, but I also do all sorts of other data integration pieces, operational tooling around the company, just making sure everything works front to back. Basically from this whole integration, from these integration approaches, I work with different people with very different mindsets, working in different paradigms. We've got closure engineers who are very much on the functional programming side and I picked up a lot of things from them that I found really useful, but obviously there's a lot of object orientation going on in Python as well and I kind of just want to share some of my learnings from bringing those together and what I found particularly useful. So there are four main things that I want to go into. They all overlap a little bit because you can't distill it fully away from each other, but their code structure, then data structures, how we deal with state handling and how we deal with multiple implementations for sort of the same concept, the same problem. Right, as some introduction, we've basically said it to a degree already, Python itself is a multi-paradigm language, unlike I mentioned closure on the functional programming side, Java is a good example on a sort of fully object oriented side. Python is very pragmatic, very in between. It doesn't buy into the dogmatic sticking to one side of it, but just brings it all together, lets you decide. And an important point that I want to make before we get started is those two paradigms are concepts. They're not a matter of syntax. So just because something is written inside of a class doesn't mean it's properly object oriented and just because it's written in a standalone functional or procedure even, does definitely not mean it's functional programming. So just a quick sort of trying to catch everyone in about the principles. Object orientation is from my point of view, just revolves around this idea of mutable data structures. That is things that we change in place that have a status of state that we change. It deals with that typically with a rich type system of classes that are interconnected, in particular class hierarchies and the principles of object orientation which are inheritance, abstraction, encapsulation and polymorphism. As much as object orientation is about mutable data structures, functional programming is about immutable data structures. And it typically relies on simple data types and uses pure functions. Pure functions means it's a function that has no side effects, uses no global variables, is sort of encapsulated in itself and is idempotent. So if you call the same function with the same inputs, you can always expect to get the same outputs no matter the state of any other part in the system. That's sort of the core principle there. And often these things are evaluated lazy so you can sort of nest your function calls before they're even executed. That's the two sides of the principle. And because this can be a bit dry, I thought I'm going to use an example going through the presentation I've chosen to pick Sudoku, which is sort of this, a way of a number crossword riddle if you like. It's a nine by nine grid with numbers from one to nine. And each row column or three by three blocks, so each of these here, should contain the numbers one through nine. What you get here on the left side, this is typically the problem that you get, and then you start filling in numbers till you've solved the whole thing. And that is the solution to the Sudoku. Now with that as a background, let's dive right in. So the first thing I want to talk about is code structure. And the way I'm going to do this is sort of compare the OO and the FP side to one another and then sort of find a sensible middle ground, maybe try to take the best of both. We're going to start from this string here, which is a Sudoku definition as per this open Sudoku website. Just to be clear how we understand this, the first nine digits are sort of the first row of the Sudoku riddle, the second line, and so forth. So this fully describes the original state. And I'm just going to run this because all the code in this thing is run live. I do have some more code files in the background just to keep the implementations short and narrow here. But that's all shared along with the slides on its own repo so you can later on have a play around with it yourself if you like as well. Okay, without further ado, one OO way to implement this would be a factory function. So I'm going to create the Sudoku class here. And for now, I'm just going to put a grid, an array on there, which is the nine by nine grid. We're going to change that later. But then I'm going to add this from string function as a static method or as a class method onto this. So I'm explicitly putting this function onto this class in an object-oriented way. And then I can use it like this with the example that we've just seen. And what I'm getting is obviously an instance of a Sudoku where the grid is this nine by nine array. This is fairly explicit, high context, which makes it very easy to find and use. One scene this kind of thing knows how to use it, how to even start it because most IDE's allow you to just go Sudoku dot and you find that functionality. Now in the functional world, how would you do it? You would actually isolate that function and write something like this. I'm going to go into the implementation a little bit more just after this. But it just stands on its own in an idempotent way. And you can use it by just applying it to your input. It gives you a grid, but it's just the grid. It doesn't know it's a Sudoku. We'll go into that a bit further down the line as well. So this is entirely free of any assumptions about the use case. And because of that, it's really easy to reuse or generalize. So I could parse completely different things from a Sudoku with that exact same function because it doesn't know of its context. So how do we bring that together? And that is a way that I like doing this is saying we have this function here. We keep the pure function with all of its benefits. In fact, I even went this far here and said, I'm going to generalize this to any square matrix and be able to parse any square matrix with this. But we can also create our Sudoku class and essentially just use this function on the class to have this sort of high context use case as well. So what we end up is we've got both. Makes our code very tidy and reusable because it's nicely chunked up. It generalizes really well because I've demonstrated it here by just generalizing this function. It works in any context. It works for any user. And because you still have the high context class, it's very easy to use and explore as well. So from my point of view, this is sort of a good way of just bringing together the best of both worlds. Okay, let's look at the implementation and you can probably guess it from the title. The object oriented or it's really more from a procedural world, but following the mutable data principle, the approach would here would be you create an empty array at first with the outputs, then you iterate over the inputs that you have and append to your outputs for every for every digit that you have in the input. Works fine. But this one here comes to mind for me. I would have written it shorter, but I didn't have the time. And that is because I find this very easy to write, but it can be a bit tedious to read and reconstruct. So looking at this, what was actually the high level intention, what was meant to be done. And then because you're fairly explicit about the variables and the appending, this can be quite error prone. And if you write this kind of thing a lot, you probably know what I mean. But what's the alternative? And that's a piece that I really like from the functional world. That is the idea of mapping, because what we really wanted to do there was take our inputs and apply this integer function to each element in the input sequence. Now, little disclaimer, int is actually a class that just acts like a function, which kind of just proves the point that Python is multi-paradigm. And I'm just going to leave that aside for now. So what are we doing here? We're mapping, so we're applying the int function over our inputs. And just because, like I said earlier, this is a lazy operation where it is forced to be a tuple, which makes it non-lazy. And then we've got our output values here. If that's a bit unclear, sort of with the order of execution, there is a trick that I like to use from the tools library, which is we can just force this to be left to right with this thread last function, which is essentially creating a pipeline from left to right saying take all raw examples, map it over the int function, and then turn it all into a tuple to collect the outputs. So this is a comparatively concise way of expressing the same thing that we had before. And it's really much closer to the intention, because what we wanted to do was just to apply this function integer to all of our inputs, which is exactly what we're saying here when I'm telling it to loop. I think that makes it really easy to read. You might disagree at this point, which I did as well when I saw it for the very first time. But once you're used to the syntax, this becomes much, much easier to read than those tedious for loops. It can take a little longer to write though, because you have to be a bit clearer about the actual intention. I have to try to abstract things a bit more. But I would argue that's a good thing, and it makes you a better software engineer. So yeah, just as a side note, Python has this great syntax of list comprehensions as well, which is a bit of a mix of both worlds, and it works really well as well. It's kind of easy to read and write, but you really have to be careful. Never use lambda functions inside of list comprehensions. Also never define functions within a for loop, by the way, because those won't behave as you want them to. I'll leave that as it stands. And if you do things like that, just make sure those don't get too long and too complicated, because you do want it to be easy to read. So pull any sort of more intricate logic out into its own function, and then use the function inside the list comprehension or inside your pipeline from earlier. All right. Just another quick example on this, the opposite of parsing, just not quite the opposite. But similarly, when we want to take our internal object and just display it in the object oriented world, we would implement this representation function, which I've done here in this class that I'm loading. And then once we've done that, it's straightforward, we just let this thing display. There's no further thing that we need to do. This is a built in. This is this behaves clever. So that makes it really easy to get a nice representation. The functional way there is a bit more explicit in that we would say, well, to really get this Sudoku all the way, I'm using the pipeline again, we're taking our raw input, we're parsing it to the internal format, then we're formatting it for display, and then we're printing it. I explicitly kept printing outside of there because printing per se is a side effect. So I don't want it in this function to keep this function pure. That being said, the output is kind of the same. Well, it's exactly the same. So the multi paradigm is always, yeah, why don't we just do both or take the best of both? And here, similar approach, just define the function as a pure function reusable, and then enhance your Sudoku class by using that function. So keep the class a bit shallower and keep your sort of reusable chunks of logic outside of it. Okay, that's as much as I want to say about the first part. Let's look a bit more at data structures. Even here, so you see the example going forward. Now we're going to use that Sudoku grid. Up till now, I just modeled it as an array. But let's see what we can actually do. In an object oriented context, you might want to do something like this. You might want to model each sort of small square with a digit as this square class. Then you might want to create an abstract square collection, which just ties a number of these together, typically nine for rows, columns and blocks. But if you want more, well, a Sudoku is really a collection of 81 squares as well, nine by nine. But to make it all nice and explicit, you would say, well, a Sudoku is also really knows its other square collections, it knows its rows, its columns, it blocks. So you've got a very explicit structure there that you can work with. So that gives us this nice instance here where we can say, well, just give me your eighth row or give me a particular square, give me all the information about it. In this case, we know where the square is, what the digit is that is currently filled in, and whether it's locked. So whether this was an input from our original thing or maybe something we filled in later on. Right. So that assumes certain usage patterns because you're putting a lot of context into it. That in turn makes it very intuitive to explore, but it's also fairly rigid, and it requires a lot of boilerplate. Proof is here, the implementation of this was 120 lines just for sort of defining the classes, setups, etc. So what's the alternative? Functional programming is all about simplicity. So here we just say, you know what, this thing is a nine by nine grid. It's a grid of shape nine by nine and of data type int. Done. So three lines instead of 120, very simplistic. We can use it in the same way as before. So after we've parsed our input, we can validate it with this thing. Again, this is a schema is a multi-paradigm language in its own right, which is why we have sort of an instance method here. So you can always do your chain with those as well, as long as they're immutable. Well, we get to that. So this is a very minimalist approach, obviously with zero or close to zero boilerplate. There is absolutely no structure, no context on the data structure itself beyond this is a nine by nine grid, which can make it a bit harder to explore, but it also makes it easier to reuse as I kind of pointed towards before. So what's the best approach? And I can obviously only give my very opinionated answer. But here we go. So why don't we create a Sudoku class that gives the whole thing some context similar like I did it before, but actually underneath that there is just a grid. So we're using the simplicity there. And we don't model all these roles, columns, collections explicitly. That's implementation detail. And we can do that in a very, very elegant simplistic way. But we then do tie it together on this class, maybe have some nice functionality on here and adds context and just brings it all together. I like to call it like a shallow wiring class or anything like that. Yes, I said that here as well. So it saves a lot of code, but it does have the context for the user. And at the same time comes with all the benefits of being able to sort of take small chunks of your logic, take them out, make them reusable and concise. Okay, on to the next part about state handling. Here it's going to be about we have the Sudoku now. We have a grid with parsed it from an input source. But now we want to start filling things in because at the end of the talk, we kind of want to have a solver that automatically solves that Sudoku. So let's think about the next step. How do we fill in the digits? And for this, I'm using a multi-paradigm implementation right away inspired by pandas and it's in place concept because I think that just showcases very nicely what mutable and immutable means and also that it's not necessarily tied to a certain syntax. It's all one class is here. So I'm just going to create a blank Sudoku here, just blank nine by nine grid. So I can show you a few operations on that. The mutable way of interacting with this is change it in place. So I've made it explicit here by saying in place equals true and we're setting at x, y, zero, zero. So at the top left corner, we're setting a seven and then well, the seven is in this Sudoku. So we've changed it in place. That seems natural. Like, I changed it. Now it's changed. It means there is no way back, no history. We're changing everything as we go along. What is the alternative? So the alternative isn't the immutable way of saying in place false. So here we're setting this digit but actually not on this Sudoku. Instead what we're getting back is this new Sudoku here with the digit for set. The big difference probably only shows up when I then go and say wait, but what was the Sudoku? And that's still as it was before where we have the seven filled in, but we don't have the four filled in yet. So we now have two different versions of it and that makes it really easy to use this, parallelize this. It's very efficient, avoids any concurrency errors because you simply have to synchronize less state between any nodes that makes it efficient and also helps with the errors. I could probably give a whole talk on why that's the case, but let's skip that aside. Because you have this before, after picture as well that gives you some natural versioning and also like I've shown before with pipelines, it lends itself very well to that, but it also in this multi-paradigm syntax with classes lends itself very well to method training. That's one thing that I want to highlight is now you can do something like this, say Sudoku, set digit, set another, set another digit and what you're getting back is obviously this change thing with these digits set, but also we've not changed the original one. So you could from there try to solve the whole thing and then go, ah, maybe I did something wrong, backtrack to the original version or to a version before you can save versions. This can be really handy in different application contexts. So my recommendation is I learned a lot from bringing more functional programming into my code and making it by that more multi-paradigm and the idea of immutable data structures really, really helps with a lot of applications I find. So try out things like a frozen data class named tuples which are essentially sort of kind of act like dictionaries and like tuples at the same time, but they're immutable. Frozen dick and frozen dick and persistent map are probably the, the equivalent, yeah, they're probably the same, which is a frozen equivalent of a dictionary. Then we've got, yeah, use multiple data structures in immutable ways as well. So if you have a dictionary, then still maybe don't set things in place, but rather use something from this tools library, which I've mentioned before because it's, it's my favorite library in Python. And there's sort of associate functions, for example, where you have, you give it your input dictionary, a key and a value, and what you get back is a new dictionary with that additional key and value pair set. Also an idea, try to keep functions pure and idempotent, try to pull them out of your classes and make them very reusable and then use classes where configuration and state is more required or desired to wire, wire it all together. Just one more example of how this works in pandas. I'm just going to create a data frame here, just a couple of random numbers, but then I can do a nice method chain here saying assign a new column, assign another new column, based on that one, you work a lot with lambdas on these, and then maybe drop some rows. And that gives me this output here. Don't worry too much about the content. It's just a dummy example. But also we still have the unchanged original data frame. And that I found extremely helpful when dealing with Jupyter notebooks, because people tend to jump around in Jupyter notebooks and not just executed sort of front to back, top down. But if you want to go back and you've changed a data frame somewhere, it can get really messy really quickly. Whereas if you treat your data in a more immutable way, then you can go back and just re-execute any cells and it won't really make a difference. You can jump around more and you have more self-contained pieces of logic, which again makes the whole thing more reusable and because it's more self-contained and more reusable, it's also closer to production ready. I've noticed this a lot. We've, well, I've talked to the wider data science team and got them to basically write all the Jupyter notebooks for new algorithms, new data analysis pieces and that more in this immutable style. And since then my job got really a lot easier off taking these things and putting them into a production system because it's just more things that are easier to take out and use. Okay. Last main part is how do we deal with multiple implementations? And here we're going to actually look at now solving the Sudoku. So one thing that I thought about in solving this was a deterministic solver based on a mask. So we would create a mask that only shows you the fields where you could put a digit in. So where could you in theory you put this digit and that per digit? So you create these masks, then fill any unambiguous ones. So where there's in the mask just one possible place where digit could be within its row column or block. And then you just keep repeating that. That alone works well for very easy Sudoku that have a clear solution, but it's actually insufficient when you get to the harder ones because they can have multiple solutions. So a deterministic solution is just not going to get you there fully. So I created a random solution as well. Also uses the same concept of a mask because we want to still solve it by the rules, right? But then we just fill a random digit and we just repeat that and keep doing it. The problem is that we often back ourselves into a corner with that approach. So we have to backtrack and rerun the whole thing. And you have to rerun it so often it makes it prohibitively slow. I think I tried with 100,000 runs that was definitely not enough and the million rows took million tries took too long for me to for my patients. So what I did instead was bring it together into a combined approach. And here we're just saying run it deterministic as long as you can. And once you sort of run out of moves, then try a random step and go back to deterministic and just keep iterating that. You might still need a few tries because of the randomness, but this is actually a really, really effective solution. It's still fairly simple, but it's an effective solution. The big question is how do we get those together? So how do we organize our code to reflect that? Let's look at the object oriented way first again. So you would probably create something like this, a hierarchy of solvers. So there's the abstract idea of a solver at the top. Then we probably want something like a step-based solver that just sort of handles iteration, retrying and running steps again and again, that sort of thing. Then one implementation of that or one sort of abstract subclass of that is a mask-based solver so that implements our logic of creating a mask and then running the step. So it kind of is a mask-based step-based solver really. So then our actual implementations of deterministic and random solver can just be underneath and those essentially now just implement the step that is taken. And then the combination would just use those two classes so it knows those mask-based solvers and combines them. A solution could look something like this or the use case could look something like this. We instantiate our Sudoku as we had it before. We instantiate a solver. We tell the solver to solve the Sudoku that we gave it and then we have a look at the solution and there it is. Let's try the combined one. Yeah, works the same. Glad. So what is the idea of that? We have a mutable data access, similar issues as before. It can make things a tiny bit faster but it's usually not worth it unless you're really in a performance critical environment and even then the difference can be minute. I do find that when you organize your code in a way like this, you end up with a lot of single method classes because it kind of just has to split the functionalities apart in order to be able to reuse them. But if you have a single method class, why is it not just a function? I struggle to understand the logic there because all you're doing is you're adding boiler plate. So it seems to me just a complicated design for simple functionality but again, similar things apply as before. It does have some use for the user because it's a straightforward way of using it. So what's just a word count for proof that it has a bit of boiler plate. What's the functional approach to that? Well we just create these functions and then sort of worry about wiring them up later. We have a function that creates a mask. We have these individual step functions, the combined step function. I forgot the arrows there actually uses the random and deterministic steps. And we've got these solve function that iterates over them. So kind of similar idea of splitting the functionality up but just broken down into the actual pieces of logic. The use case looks a bit different and you kind of need more context there to be able to run this. So we can wire, pre-wire a function called solve combined by saying it's solved with this combined step and then we can run it like we did before with our pipeline taking a raw example, parse it, solve it, format it, print it and there we go. Same output and output. I think some feedback here at the talk back channel. Okay, brilliant, thank you. So let's see. So this makes the responsibilities very clear per function. It's a very simple pragmatic design. Once you know how to use it, it's very easy to introspect and combine in different ways as well and it's much more concise. The code itself is more concise and on top of that we don't actually have any of the base module with the boilerplate code so we're really saving a lot of code writing here. But like I said, you have to know a bit more about the library to do your wiring. So where do we go from that? What's the multi-paradigm solution? And here you could opt for a class like we've done before to do the wiring but I kind of just wanted to showcase that you can do it in a slightly different way, this time leaning a bit more to the functional side than the object side to do this. And that is I've created a solving function here that does the wiring mainly in a functional way. But we're still using the Sudoku, the higher context class to represent the actual Sudoku, what we're running this as solving function over it. So we've basically flipped it around rather than putting a pure function on a class, we're now using the class as an input and the pure function is the sort of top level entry point. And yeah, take my word for it, this runs perfectly fine. So this brings together the simplicity and clarity of the functional programming, but it also makes sure you have all the high context of a Sudoku that makes it really easy to explore the actual Sudoku itself. And then you just want to solve with the input of that class you've prepared. If you really want, you can sort of create, I'd call this a solving configuration, where you say we've got a step function here, we've got a maximum number of tries, and then we're just going to use the solve function that I just showed you and put it on a class. So I'm making this callable here as well. So I wouldn't necessarily recommend this, I just want to showcase here that you can sort of make the lines blur between what's a function and what's a class with this callable syntax. And here we can say, well, take our raw example, this time parse it with the from string method on our multi-paradigm class, and then thread this class into a solving configuration using the combined step and trying a maximum number of 100 times. So that works perfectly fine. And like I said, this is just now giving you some ideas on how you can combine things together. Right, that's the main part. I just want to give some key takeaways and then open up for questions. I'm going to do this fairly quickly though, because we're running short on time. So object orientation, just some observations. It's typically your fairly top-down design, you create larger, very topical structures and you're quite explicit about high context. You bring functionality and data together in a topical way. It leads to very intuitive use case that makes it all very explorable. Functional programming is very much the other way around, going more in a bottom-up fashion, tries to simplify everything as much as possible and put it into small chunks of things that you can actually reuse, functions that you can reuse, and then are entirely separate from the data. So there's a high isolation and we have a low context. That typically leads to very reusable things. Tidy and concise code, I find if you do it right. And the use cases are a bit more flexible. Just a quick side note, to keep your code tidy and concise, you do need to work a bit more with modules than you would have to in a purely object-oriented way. But there's enough means and ways to structure your code. So what does that mean for multi-paradigm? It's a pick and mix of both worlds. Warning, you can pick and mix the worst of both worlds. Try not to do that. I think I've shown you probably some useful ways of combining. Use pure functions in a mutable context. Just bring that all together. Good thing is, no side effects, no problems. You can always use the pure functions in a mutable context and you shouldn't get any problems. The other way around when you use something that works with mutable data in an immutable context, then you want to make sure you're explicit about it and use a copy and modify pattern. Just to make sure you're not changing your inputs and by that, keep it immutable for the context that is required. So ideally, you end up with both intuitive and flexible use cases and you end up with both something that's explorable and has very reusable components. We have four minutes left. Okay, perfect. That is including Q&A. So if you want to do Q&A, then we should switch to the next one. Okay, thanks Mark. Yeah, so a favorite approach, iterate with a REPL. That's where I'm going to wrap up. So we have some time for questions. Okay, excellent. Thank you very much, Elias. That was a very, very interesting talk. Sorry, I had to cut you a bit short. We do have a couple of questions. We don't have, we have three minutes left for questions, so I'm just going to read them from top to bottom. So first question there, how do you recommend organizing your helper functions in a module? That depends very much on what the context is about. So I would generally start writing them in one module, whatever you use them for, and then sort of start breaking them out when you realize, hey, this is something that I could reuse, maybe put it into a utility library or into a topical library or even just in a sub module, pull them out when it starts to look like, hey, this is not just for this particular use case. This could or will be more useful in others as well. Okay, great. Thanks. Next question is why data class and not named tuple? I use data classes here to just short, get rid of some boilerplate. I showed you the 120 lines that was already with using data classes. So I used them mostly for the object-oriented context here and then just kept going to keep it sort of similar in the talk. Frozen data classes and named tuples, really, if you just use them this way, there is no considerable difference. I've definitely used named tuples as well with some functionality on them. Okay, great. So one more question. Why should you not use lambda inside list current prehensions just for readability? No, not just for a readability. There is a, I don't know what the correct term there is, but there is a problem with context generally that a for loop doesn't have a context. So if you end up, for example, defining slight tangent, if you define a function inside a for loop and then you sort of memorize that function for later use, you're actually going to override the function and only be left with the one of the last iteration of the loop. So you can really get yourself into some unexpected behavior there. And that same principle translates to lambda functions inside a loop. So if you directly execute something in a list comprehension, you don't actually need the lambda, but as soon as you start to do something, say with pandas inside a loop with a lambda df and then use something on the data frame, it will not do what you wanted to do. So just try to avoid it. Okay, thank you very much. I think that was the last question that we can take. I would like to ask the attendees who had more questions to go to the talk channel that we have for this talk, which is talk write multi paradigm code. And then you can ask additional questions there and Elias can then answer those. So thank you very much Elias for the nice session. Let me try to run a short applause for you.
 Okay, so now we have 10 o'clock and we can start with the next session.  We have Elias Mislas, who is going to join us and give a talk about how to write multi-paradigm  code without making a mess.  Elias is a principal machine learning engineer from Previz, I hope I pronounced that correctly.  He is doing a lot of machine learning and he is going to talk about using both object  orientation and functional programming together and how to make those two play nicely.  So let's head on to Elias.  Elias, can you please start sharing your screen?  Of course, of course.  The second second, right, right.  Yeah, yeah.  Excellent.  Okay, well, thank you, Mark, for the introduction and thank you, everyone, for joining.  What is now the first talk here?  So as Mark already introduced this about multi-paradigm programming in Python, I've got an introduction  here.  It's basically already said, Previz, the company I work with is an invoicing finance company  and we use machine learning on large corporate data sets to predict whether invoices will  be paid in the future and then to finance invoices and improve the cash flow of small  and medium enterprises.  I myself am a machine or yeah, machine learning engineer principal.  And my main responsibility is sort of integrating our machine learning algorithms into our invoice  processing platform, but I also do all sorts of other data integration pieces, operational  tooling around the company, just making sure everything works front to back.  Basically from this whole integration, from these integration approaches, I work with  different people with very different mindsets, working in different paradigms.  We've got closure engineers who are very much on the functional programming side and I picked  up a lot of things from them that I found really useful, but obviously there's a lot  of object orientation going on in Python as well and I kind of just want to share some  of my learnings from bringing those together and what I found particularly useful.  So there are four main things that I want to go into.  They all overlap a little bit because you can't distill it fully away from each other,  but their code structure, then data structures, how we deal with state handling and how we  deal with multiple implementations for sort of the same concept, the same problem.  Right, as some introduction, we've basically said it to a degree already, Python itself  is a multi-paradigm language, unlike I mentioned closure on the functional programming side,  Java is a good example on a sort of fully object oriented side.  Python is very pragmatic, very in between.  It doesn't buy into the dogmatic sticking to one side of it, but just brings it all  together, lets you decide.  And an important point that I want to make before we get started is those two paradigms  are concepts.  They're not a matter of syntax.  So just because something is written inside of a class doesn't mean it's properly object  oriented and just because it's written in a standalone functional or procedure even,  does definitely not mean it's functional programming.  So just a quick sort of trying to catch everyone in about the principles.  Object orientation is from my point of view, just revolves around this idea of mutable  data structures.  That is things that we change in place that have a status of state that we change.  It deals with that typically with a rich type system of classes that are interconnected,  in particular class hierarchies and the principles of object orientation which are inheritance,  abstraction, encapsulation and polymorphism.  As much as object orientation is about mutable data structures, functional programming is  about immutable data structures.  And it typically relies on simple data types and uses pure functions.  Pure functions means it's a function that has no side effects, uses no global variables,  is sort of encapsulated in itself and is idempotent.  So if you call the same function with the same inputs, you can always expect to get  the same outputs no matter the state of any other part in the system.  That's sort of the core principle there.  And often these things are evaluated lazy so you can sort of nest your function calls  before they're even executed.  That's the two sides of the principle.  And because this can be a bit dry, I thought I'm going to use an example going through  the presentation I've chosen to pick Sudoku, which is sort of this, a way of a number  crossword riddle if you like.  It's a nine by nine grid with numbers from one to nine.  And each row column or three by three blocks, so each of these here, should contain the  numbers one through nine.  What you get here on the left side, this is typically the problem that you get, and then  you start filling in numbers till you've solved the whole thing.  And that is the solution to the Sudoku.  Now with that as a background, let's dive right in.  So the first thing I want to talk about is code structure.  And the way I'm going to do this is sort of compare the OO and the FP side to one another  and then sort of find a sensible middle ground, maybe try to take the best of both.  We're going to start from this string here, which is a Sudoku definition as per this  open Sudoku website.  Just to be clear how we understand this, the first nine digits are sort of the first row  of the Sudoku riddle, the second line, and so forth.  So this fully describes the original state.  And I'm just going to run this because all the code in this thing is run live.  I do have some more code files in the background just to keep the implementations short and  narrow here.  But that's all shared along with the slides on its own repo so you can later on have a  play around with it yourself if you like as well.  Okay, without further ado, one OO way to implement this would be a factory function.  So I'm going to create the Sudoku class here.  And for now, I'm just going to put a grid, an array on there, which is the nine by nine  grid.  We're going to change that later.  But then I'm going to add this from string function as a static method or as a class  method onto this.  So I'm explicitly putting this function onto this class in an object-oriented way.  And then I can use it like this with the example that we've just seen.  And what I'm getting is obviously an instance of a Sudoku where the grid is this nine by  nine array.  This is fairly explicit, high context, which makes it very easy to find and use.  One scene this kind of thing knows how to use it, how to even start it because most  IDE's allow you to just go Sudoku dot and you find that functionality.  Now in the functional world, how would you do it?  You would actually isolate that function and write something like this.  I'm going to go into the implementation a little bit more just after this.  But it just stands on its own in an idempotent way.  And you can use it by just applying it to your input.  It gives you a grid, but it's just the grid.  It doesn't know it's a Sudoku.  We'll go into that a bit further down the line as well.  So this is entirely free of any assumptions about the use case.  And because of that, it's really easy to reuse or generalize.  So I could parse completely different things from a Sudoku with that exact same function  because it doesn't know of its context.  So how do we bring that together?  And that is a way that I like doing this is saying we have this function here.  We keep the pure function with all of its benefits.  In fact, I even went this far here and said, I'm going to generalize this to any square  matrix and be able to parse any square matrix with this.  But we can also create our Sudoku class and essentially just use this function on the  class to have this sort of high context use case as well.  So what we end up is we've got both.  Makes our code very tidy and reusable because it's nicely chunked up.  It generalizes really well because I've demonstrated it here by just generalizing this function.  It works in any context.  It works for any user.  And because you still have the high context class, it's very easy to use and explore as  well.  So from my point of view, this is sort of a good way of just bringing together the best  of both worlds.  Okay, let's look at the implementation and you can probably guess it from the title.  The object oriented or it's really more from a procedural world, but following the mutable  data principle, the approach would here would be you create an empty array at first with  the outputs, then you iterate over the inputs that you have and append to your outputs for  every for every digit that you have in the input.  Works fine.  But this one here comes to mind for me.  I would have written it shorter, but I didn't have the time.  And that is because I find this very easy to write, but it can be a bit tedious to read  and reconstruct.  So looking at this, what was actually the high level intention, what was meant to be  done.  And then because you're fairly explicit about the variables and the appending, this can  be quite error prone.  And if you write this kind of thing a lot, you probably know what I mean.  But what's the alternative?  And that's a piece that I really like from the functional world.  That is the idea of mapping, because what we really wanted to do there was take our inputs  and apply this integer function to each element in the input sequence.  Now, little disclaimer, int is actually a class that just acts like a function, which  kind of just proves the point that Python is multi-paradigm.  And I'm just going to leave that aside for now.  So what are we doing here?  We're mapping, so we're applying the int function over our inputs.  And just because, like I said earlier, this is a lazy operation where it is forced to  be a tuple, which makes it non-lazy.  And then we've got our output values here.  If that's a bit unclear, sort of with the order of execution, there is a trick that  I like to use from the tools library, which is we can just force this to be left to right  with this thread last function, which is essentially creating a pipeline from left  to right saying take all raw examples, map it over the int function, and then turn it  all into a tuple to collect the outputs.  So this is a comparatively concise way of expressing the same thing that we had before.  And it's really much closer to the intention, because what we wanted to do was just to apply  this function integer to all of our inputs, which is exactly what we're saying here when  I'm telling it to loop.  I think that makes it really easy to read.  You might disagree at this point, which I did as well when I saw it for the very first  time.  But once you're used to the syntax, this becomes much, much easier to read than those  tedious for loops.  It can take a little longer to write though, because you have to be a bit clearer about  the actual intention.  I have to try to abstract things a bit more.  But I would argue that's a good thing, and it makes you a better software engineer.  So yeah, just as a side note, Python has this great syntax of list comprehensions as well,  which is a bit of a mix of both worlds, and it works really well as well.  It's kind of easy to read and write, but you really have to be careful.  Never use lambda functions inside of list comprehensions.  Also never define functions within a for loop, by the way, because those won't behave as  you want them to.  I'll leave that as it stands.  And if you do things like that, just make sure those don't get too long and too complicated,  because you do want it to be easy to read.  So pull any sort of more intricate logic out into its own function, and then use the function  inside the list comprehension or inside your pipeline from earlier.  All right.  Just another quick example on this, the opposite of parsing, just not quite the opposite.  But similarly, when we want to take our internal object and just display it in the object oriented  world, we would implement this representation function, which I've done here in this class  that I'm loading.  And then once we've done that, it's straightforward, we just let this thing display.  There's no further thing that we need to do.  This is a built in.  This is this behaves clever.  So that makes it really easy to get a nice representation.  The functional way there is a bit more explicit in that we would say, well, to really get  this Sudoku all the way, I'm using the pipeline again, we're taking our raw input, we're parsing  it to the internal format, then we're formatting it for display, and then we're printing it.  I explicitly kept printing outside of there because printing per se is a side effect.  So I don't want it in this function to keep this function pure.  That being said, the output is kind of the same.  Well, it's exactly the same.  So the multi paradigm is always, yeah, why don't we just do both or take the best of  both?  And here, similar approach, just define the function as a pure function reusable, and  then enhance your Sudoku class by using that function.  So keep the class a bit shallower and keep your sort of reusable chunks of logic outside  of it.  Okay, that's as much as I want to say about the first part.  Let's look a bit more at data structures.  Even here, so you see the example going forward.  Now we're going to use that Sudoku grid.  Up till now, I just modeled it as an array.  But let's see what we can actually do.  In an object oriented context, you might want to do something like this.  You might want to model each sort of small square with a digit as this square class.  Then you might want to create an abstract square collection, which just ties a number  of these together, typically nine for rows, columns and blocks.  But if you want more, well, a Sudoku is really a collection of 81 squares as well, nine by  nine.  But to make it all nice and explicit, you would say, well, a Sudoku is also really knows  its other square collections, it knows its rows, its columns, it blocks.  So you've got a very explicit structure there that you can work with.  So that gives us this nice instance here where we can say, well, just give me your eighth  row or give me a particular square, give me all the information about it.  In this case, we know where the square is, what the digit is that is currently filled  in, and whether it's locked.  So whether this was an input from our original thing or maybe something we filled in later  on.  Right.  So that assumes certain usage patterns because you're putting a lot of context into it.  That in turn makes it very intuitive to explore, but it's also fairly rigid, and it requires  a lot of boilerplate.  Proof is here, the implementation of this was 120 lines just for sort of defining the  classes, setups, etc.  So what's the alternative?  Functional programming is all about simplicity.  So here we just say, you know what, this thing is a nine by nine grid.  It's a grid of shape nine by nine and of data type int.  Done.  So three lines instead of 120, very simplistic.  We can use it in the same way as before.  So after we've parsed our input, we can validate it with this thing.  Again, this is a schema is a multi-paradigm language in its own right, which is why we  have sort of an instance method here.  So you can always do your chain with those as well, as long as they're immutable.  Well, we get to that.  So this is a very minimalist approach, obviously with zero or close to zero boilerplate.  There is absolutely no structure, no context on the data structure itself beyond this is  a nine by nine grid, which can make it a bit harder to explore, but it also makes it easier  to reuse as I kind of pointed towards before.  So what's the best approach?  And I can obviously only give my very opinionated answer.  But here we go.  So why don't we create a Sudoku class that gives the whole thing some context similar  like I did it before, but actually underneath that there is just a grid.  So we're using the simplicity there.  And we don't model all these roles, columns, collections explicitly.  That's implementation detail.  And we can do that in a very, very elegant simplistic way.  But we then do tie it together on this class, maybe have some nice functionality on here  and adds context and just brings it all together.  I like to call it like a shallow wiring class or anything like that.  Yes, I said that here as well.  So it saves a lot of code, but it does have the context for the user.  And at the same time comes with all the benefits of being able to sort of take small chunks  of your logic, take them out, make them reusable and concise.  Okay, on to the next part about state handling.  Here it's going to be about we have the Sudoku now.  We have a grid with parsed it from an input source.  But now we want to start filling things in because at the end of the talk, we kind of  want to have a solver that automatically solves that Sudoku.  So let's think about the next step.  How do we fill in the digits?  And for this, I'm using a multi-paradigm implementation right away inspired by pandas  and it's in place concept because I think that just showcases very nicely what mutable  and immutable means and also that it's not necessarily tied to a certain syntax.  It's all one class is here.  So I'm just going to create a blank Sudoku here, just blank nine by nine grid.  So I can show you a few operations on that.  The mutable way of interacting with this is change it in place.  So I've made it explicit here by saying in place equals true and we're setting at x,  y, zero, zero.  So at the top left corner, we're setting a seven and then well, the seven is in this  Sudoku.  So we've changed it in place.  That seems natural.  Like, I changed it.  Now it's changed.  It means there is no way back, no history.  We're changing everything as we go along.  What is the alternative?  So the alternative isn't the immutable way of saying in place false.  So here we're setting this digit but actually not on this Sudoku.  Instead what we're getting back is this new Sudoku here with the digit for set.  The big difference probably only shows up when I then go and say wait, but what was  the Sudoku?  And that's still as it was before where we have the seven filled in, but we don't have  the four filled in yet.  So we now have two different versions of it and that makes it really easy to use this,  parallelize this.  It's very efficient, avoids any concurrency errors because you simply have to synchronize  less state between any nodes that makes it efficient and also helps with the errors.  I could probably give a whole talk on why that's the case, but let's skip that aside.  Because you have this before, after picture as well that gives you some natural versioning  and also like I've shown before with pipelines, it lends itself very well to that, but it  also in this multi-paradigm syntax with classes lends itself very well to method training.  That's one thing that I want to highlight is now you can do something like this, say  Sudoku, set digit, set another, set another digit and what you're getting back is obviously  this change thing with these digits set, but also we've not changed the original one.  So you could from there try to solve the whole thing and then go, ah, maybe I did something  wrong, backtrack to the original version or to a version before you can save versions.  This can be really handy in different application contexts.  So my recommendation is I learned a lot from bringing more functional programming into my  code and making it by that more multi-paradigm and the idea of immutable data structures  really, really helps with a lot of applications I find.  So try out things like a frozen data class named tuples which are essentially sort of  kind of act like dictionaries and like tuples at the same time, but they're immutable.  Frozen dick and frozen dick and persistent map are probably the, the equivalent, yeah,  they're probably the same, which is a frozen equivalent of a dictionary.  Then we've got, yeah, use multiple data structures in immutable ways as well.  So if you have a dictionary, then still maybe don't set things in place, but rather use  something from this tools library, which I've mentioned before because it's, it's my favorite  library in Python.  And there's sort of associate functions, for example, where you have, you give it your  input dictionary, a key and a value, and what you get back is a new dictionary with that  additional key and value pair set.  Also an idea, try to keep functions pure and idempotent, try to pull them out of your  classes and make them very reusable and then use classes where configuration and state  is more required or desired to wire, wire it all together.  Just one more example of how this works in pandas.  I'm just going to create a data frame here, just a couple of random numbers, but then  I can do a nice method chain here saying assign a new column, assign another new column,  based on that one, you work a lot with lambdas on these, and then maybe drop some rows.  And that gives me this output here.  Don't worry too much about the content.  It's just a dummy example.  But also we still have the unchanged original data frame.  And that I found extremely helpful when dealing with Jupyter notebooks, because people tend  to jump around in Jupyter notebooks and not just executed sort of front to back, top  down.  But if you want to go back and you've changed a data frame somewhere, it can get really  messy really quickly.  Whereas if you treat your data in a more immutable way, then you can go back and just re-execute  any cells and it won't really make a difference.  You can jump around more and you have more self-contained pieces of logic, which again  makes the whole thing more reusable and because it's more self-contained and more reusable,  it's also closer to production ready.  I've noticed this a lot.  We've, well, I've talked to the wider data science team and got them to basically write  all the Jupyter notebooks for new algorithms, new data analysis pieces and that more in  this immutable style.  And since then my job got really a lot easier off taking these things and putting them into  a production system because it's just more things that are easier to take out and use.  Okay.  Last main part is how do we deal with multiple implementations?  And here we're going to actually look at now solving the Sudoku.  So one thing that I thought about in solving this was a deterministic solver based on a  mask.  So we would create a mask that only shows you the fields where you could put a digit  in.  So where could you in theory you put this digit and that per digit?  So you create these masks, then fill any unambiguous ones.  So where there's in the mask just one possible place where digit could be within its row  column or block.  And then you just keep repeating that.  That alone works well for very easy Sudoku that have a clear solution, but it's actually  insufficient when you get to the harder ones because they can have multiple solutions.  So a deterministic solution is just not going to get you there fully.  So I created a random solution as well.  Also uses the same concept of a mask because we want to still solve it by the rules, right?  But then we just fill a random digit and we just repeat that and keep doing it.  The problem is that we often back ourselves into a corner with that approach.  So we have to backtrack and rerun the whole thing.  And you have to rerun it so often it makes it prohibitively slow.  I think I tried with 100,000 runs that was definitely not enough and the million rows  took million tries took too long for me to for my patients.  So what I did instead was bring it together into a combined approach.  And here we're just saying run it deterministic as long as you can.  And once you sort of run out of moves, then try a random step and go back to deterministic  and just keep iterating that.  You might still need a few tries because of the randomness, but this is actually a really,  really effective solution.  It's still fairly simple, but it's an effective solution.  The big question is how do we get those together?  So how do we organize our code to reflect that?  Let's look at the object oriented way first again.  So you would probably create something like this, a hierarchy of solvers.  So there's the abstract idea of a solver at the top.  Then we probably want something like a step-based solver that just sort of handles iteration,  retrying and running steps again and again, that sort of thing.  Then one implementation of that or one sort of abstract subclass of that is a mask-based  solver so that implements our logic of creating a mask and then running the step.  So it kind of is a mask-based step-based solver really.  So then our actual implementations of deterministic and random solver can just be underneath and  those essentially now just implement the step that is taken.  And then the combination would just use those two classes so it knows those mask-based solvers  and combines them.  A solution could look something like this or the use case could look something like  this.  We instantiate our Sudoku as we had it before.  We instantiate a solver.  We tell the solver to solve the Sudoku that we gave it and then we have a look at the  solution and there it is.  Let's try the combined one.  Yeah, works the same.  Glad.  So what is the idea of that?  We have a mutable data access, similar issues as before.  It can make things a tiny bit faster but it's usually not worth it unless you're really  in a performance critical environment and even then the difference can be minute.  I do find that when you organize your code in a way like this, you end up with a lot  of single method classes because it kind of just has to split the functionalities apart  in order to be able to reuse them.  But if you have a single method class, why is it not just a function?  I struggle to understand the logic there because all you're doing is you're adding boiler  plate.  So it seems to me just a complicated design for simple functionality but again, similar  things apply as before.  It does have some use for the user because it's a straightforward way of using it.  So what's just a word count for proof that it has a bit of boiler plate.  What's the functional approach to that?  Well we just create these functions and then sort of worry about wiring them up later.  We have a function that creates a mask.  We have these individual step functions, the combined step function.  I forgot the arrows there actually uses the random and deterministic steps.  And we've got these solve function that iterates over them.  So kind of similar idea of splitting the functionality up but just broken down into the actual pieces  of logic.  The use case looks a bit different and you kind of need more context there to be able  to run this.  So we can wire, pre-wire a function called solve combined by saying it's solved with  this combined step and then we can run it like we did before with our pipeline taking  a raw example, parse it, solve it, format it, print it and there we go.  Same output and output.  I think some feedback here at the talk back channel.  Okay, brilliant, thank you.  So let's see.  So this makes the responsibilities very clear per function.  It's a very simple pragmatic design.  Once you know how to use it, it's very easy to introspect and combine in different ways  as well and it's much more concise.  The code itself is more concise and on top of that we don't actually have any of the  base module with the boilerplate code so we're really saving a lot of code writing here.  But like I said, you have to know a bit more about the library to do your wiring.  So where do we go from that?  What's the multi-paradigm solution?  And here you could opt for a class like we've done before to do the wiring but I kind of  just wanted to showcase that you can do it in a slightly different way, this time leaning  a bit more to the functional side than the object side to do this.  And that is I've created a solving function here that does the wiring mainly in a functional  way.  But we're still using the Sudoku, the higher context class to represent the actual Sudoku,  what we're running this as solving function over it.  So we've basically flipped it around rather than putting a pure function on a class, we're  now using the class as an input and the pure function is the sort of top level entry point.  And yeah, take my word for it, this runs perfectly fine.  So this brings together the simplicity and clarity of the functional programming, but  it also makes sure you have all the high context of a Sudoku that makes it really easy to explore  the actual Sudoku itself.  And then you just want to solve with the input of that class you've prepared.  If you really want, you can sort of create, I'd call this a solving configuration, where  you say we've got a step function here, we've got a maximum number of tries, and then we're  just going to use the solve function that I just showed you and put it on a class.  So I'm making this callable here as well.  So I wouldn't necessarily recommend this, I just want to showcase here that you can  sort of make the lines blur between what's a function and what's a class with this callable  syntax.  And here we can say, well, take our raw example, this time parse it with the from string method  on our multi-paradigm class, and then thread this class into a solving configuration using  the combined step and trying a maximum number of 100 times.  So that works perfectly fine.  And like I said, this is just now giving you some ideas on how you can combine things together.  Right, that's the main part.  I just want to give some key takeaways and then open up for questions.  I'm going to do this fairly quickly though, because we're running short on time.  So object orientation, just some observations.  It's typically your fairly top-down design, you create larger, very topical structures  and you're quite explicit about high context.  You bring functionality and data together in a topical way.  It leads to very intuitive use case that makes it all very explorable.  Functional programming is very much the other way around, going more in a bottom-up fashion,  tries to simplify everything as much as possible and put it into small chunks of things that  you can actually reuse, functions that you can reuse, and then are entirely separate  from the data.  So there's a high isolation and we have a low context.  That typically leads to very reusable things.  Tidy and concise code, I find if you do it right.  And the use cases are a bit more flexible.  Just a quick side note, to keep your code tidy and concise, you do need to work a bit  more with modules than you would have to in a purely object-oriented way.  But there's enough means and ways to structure your code.  So what does that mean for multi-paradigm?  It's a pick and mix of both worlds.  Warning, you can pick and mix the worst of both worlds.  Try not to do that.  I think I've shown you probably some useful ways of combining.  Use pure functions in a mutable context.  Just bring that all together.  Good thing is, no side effects, no problems.  You can always use the pure functions in a mutable context and you shouldn't get any  problems.  The other way around when you use something that works with mutable data in an immutable  context, then you want to make sure you're explicit about it and use a copy and modify  pattern.  Just to make sure you're not changing your inputs and by that, keep it immutable for  the context that is required.  So ideally, you end up with both intuitive and flexible use cases and you end up with  both something that's explorable and has very reusable components.  We have four minutes left.  Okay, perfect.  That is including Q&A.  So if you want to do Q&A, then we should switch to the next one.  Okay, thanks Mark.  Yeah, so a favorite approach, iterate with a REPL.  That's where I'm going to wrap up.  So we have some time for questions.  Okay, excellent.  Thank you very much, Elias.  That was a very, very interesting talk.  Sorry, I had to cut you a bit short.  We do have a couple of questions.  We don't have, we have three minutes left for questions, so I'm just going to read them  from top to bottom.  So first question there, how do you recommend organizing your helper functions in a module?  That depends very much on what the context is about.  So I would generally start writing them in one module, whatever you use them for, and  then sort of start breaking them out when you realize, hey, this is something that I  could reuse, maybe put it into a utility library or into a topical library or even just in  a sub module, pull them out when it starts to look like, hey, this is not just for this  particular use case.  This could or will be more useful in others as well.  Okay, great.  Thanks.  Next question is why data class and not named tuple?  I use data classes here to just short, get rid of some boilerplate.  I showed you the 120 lines that was already with using data classes.  So I used them mostly for the object-oriented context here and then just kept going to keep  it sort of similar in the talk.  Frozen data classes and named tuples, really, if you just use them this way, there is no  considerable difference.  I've definitely used named tuples as well with some functionality on them.  Okay, great.  So one more question.  Why should you not use lambda inside list current prehensions just for readability?  No, not just for a readability.  There is a, I don't know what the correct term there is, but there is a problem with  context generally that a for loop doesn't have a context.  So if you end up, for example, defining slight tangent, if you define a function inside a  for loop and then you sort of memorize that function for later use, you're actually going  to override the function and only be left with the one of the last iteration of the  loop.  So you can really get yourself into some unexpected behavior there.  And that same principle translates to lambda functions inside a loop.  So if you directly execute something in a list comprehension, you don't actually need  the lambda, but as soon as you start to do something, say with pandas inside a loop  with a lambda df and then use something on the data frame, it will not do what you wanted  to do.  So just try to avoid it.  Okay, thank you very much.  I think that was the last question that we can take.  I would like to ask the attendees who had more questions to go to the talk channel that  we have for this talk, which is talk write multi paradigm code.  And then you can ask additional questions there and Elias can then answer those.  So thank you very much Elias for the nice session.  Let me try to run a short applause for you.
Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_0_71.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_72_155.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_156_347.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_348_527.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_528_635.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_636_1067.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_1068_1379.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_1380_1427.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_1428_1499.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_1500_1547.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_1548_62687.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_62688_62735.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_62736_62783.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_62784_62831.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_62832_62939.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_62940_62987.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_62988_63095.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_63096_63155.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_63156_63251.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_63252_63311.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_63312_63551.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_63552_63599.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_63600_63647.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_63648_63719.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_63720_63851.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_63852_64211.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_64212_64343.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_64344_64475.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_64476_64643.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_64644_64691.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_64692_64967.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_64968_65063.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_65064_65135.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_65136_65183.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_65184_65279.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_65280_65339.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_65340_65531.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_65532_65579.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_65580_65699.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_65700_65891.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_65892_65987.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_65988_66059.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_66060_66239.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_66240_66311.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_66312_66383.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_66384_66431.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_66432_66515.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_66516_66707.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_66708_66779.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_66780_66827.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_66828_66911.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_66912_67139.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_67140_67283.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_67284_67463.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_67464_67691.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_67692_67823.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_67824_67931.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_67932_68087.png Elias_Mistler_-_How_to_write_multi-paradigm_code_RHPFPhaLu58_68088_68161.png
Okay, so now we have 10 o'clock and we can start with the next session. We have Elias Mislas, who is going to join us and give a talk about how to write multi-paradigm code without making a mess. Elias is a principal machine learning engineer from Previz, I hope I pronounced that correctly. He is doing a lot of machine learning and he is going to talk about using both object orientation and functional programming together and how to make those two play nicely. So let's head on to Elias. Elias, can you please start sharing your screen? Of course, of course. The second second, right, right. Yeah, yeah. Excellent. Okay, well, thank you, Mark, for the introduction and thank you, everyone, for joining. What is now the first talk here? So as Mark already introduced this about multi-paradigm programming in Python, I've got an introduction here. It's basically already said, Previz, the company I work with is an invoicing finance company and we use machine learning on large corporate data sets to predict whether invoices will be paid in the future and then to finance invoices and improve the cash flow of small and medium enterprises. I myself am a machine or yeah, machine learning engineer principal. And my main responsibility is sort of integrating our machine learning algorithms into our invoice processing platform, but I also do all sorts of other data integration pieces, operational tooling around the company, just making sure everything works front to back. Basically from this whole integration, from these integration approaches, I work with different people with very different mindsets, working in different paradigms. We've got closure engineers who are very much on the functional programming side and I picked up a lot of things from them that I found really useful, but obviously there's a lot of object orientation going on in Python as well and I kind of just want to share some of my learnings from bringing those together and what I found particularly useful. So there are four main things that I want to go into. They all overlap a little bit because you can't distill it fully away from each other, but their code structure, then data structures, how we deal with state handling and how we deal with multiple implementations for sort of the same concept, the same problem. Right, as some introduction, we've basically said it to a degree already, Python itself is a multi-paradigm language, unlike I mentioned closure on the functional programming side, Java is a good example on a sort of fully object oriented side. Python is very pragmatic, very in between. It doesn't buy into the dogmatic sticking to one side of it, but just brings it all together, lets you decide. And an important point that I want to make before we get started is those two paradigms are concepts. They're not a matter of syntax. So just because something is written inside of a class doesn't mean it's properly object oriented and just because it's written in a standalone functional or procedure even, does definitely not mean it's functional programming. So just a quick sort of trying to catch everyone in about the principles. Object orientation is from my point of view, just revolves around this idea of mutable data structures. That is things that we change in place that have a status of state that we change. It deals with that typically with a rich type system of classes that are interconnected, in particular class hierarchies and the principles of object orientation which are inheritance, abstraction, encapsulation and polymorphism. As much as object orientation is about mutable data structures, functional programming is about immutable data structures. And it typically relies on simple data types and uses pure functions. Pure functions means it's a function that has no side effects, uses no global variables, is sort of encapsulated in itself and is idempotent. So if you call the same function with the same inputs, you can always expect to get the same outputs no matter the state of any other part in the system. That's sort of the core principle there. And often these things are evaluated lazy so you can sort of nest your function calls before they're even executed. That's the two sides of the principle. And because this can be a bit dry, I thought I'm going to use an example going through the presentation I've chosen to pick Sudoku, which is sort of this, a way of a number crossword riddle if you like. It's a nine by nine grid with numbers from one to nine. And each row column or three by three blocks, so each of these here, should contain the numbers one through nine. What you get here on the left side, this is typically the problem that you get, and then you start filling in numbers till you've solved the whole thing. And that is the solution to the Sudoku. Now with that as a background, let's dive right in. So the first thing I want to talk about is code structure. And the way I'm going to do this is sort of compare the OO and the FP side to one another and then sort of find a sensible middle ground, maybe try to take the best of both. We're going to start from this string here, which is a Sudoku definition as per this open Sudoku website. Just to be clear how we understand this, the first nine digits are sort of the first row of the Sudoku riddle, the second line, and so forth. So this fully describes the original state. And I'm just going to run this because all the code in this thing is run live. I do have some more code files in the background just to keep the implementations short and narrow here. But that's all shared along with the slides on its own repo so you can later on have a play around with it yourself if you like as well. Okay, without further ado, one OO way to implement this would be a factory function. So I'm going to create the Sudoku class here. And for now, I'm just going to put a grid, an array on there, which is the nine by nine grid. We're going to change that later. But then I'm going to add this from string function as a static method or as a class method onto this. So I'm explicitly putting this function onto this class in an object-oriented way. And then I can use it like this with the example that we've just seen. And what I'm getting is obviously an instance of a Sudoku where the grid is this nine by nine array. This is fairly explicit, high context, which makes it very easy to find and use. One scene this kind of thing knows how to use it, how to even start it because most IDE's allow you to just go Sudoku dot and you find that functionality. Now in the functional world, how would you do it? You would actually isolate that function and write something like this. I'm going to go into the implementation a little bit more just after this. But it just stands on its own in an idempotent way. And you can use it by just applying it to your input. It gives you a grid, but it's just the grid. It doesn't know it's a Sudoku. We'll go into that a bit further down the line as well. So this is entirely free of any assumptions about the use case. And because of that, it's really easy to reuse or generalize. So I could parse completely different things from a Sudoku with that exact same function because it doesn't know of its context. So how do we bring that together? And that is a way that I like doing this is saying we have this function here. We keep the pure function with all of its benefits. In fact, I even went this far here and said, I'm going to generalize this to any square matrix and be able to parse any square matrix with this. But we can also create our Sudoku class and essentially just use this function on the class to have this sort of high context use case as well. So what we end up is we've got both. Makes our code very tidy and reusable because it's nicely chunked up. It generalizes really well because I've demonstrated it here by just generalizing this function. It works in any context. It works for any user. And because you still have the high context class, it's very easy to use and explore as well. So from my point of view, this is sort of a good way of just bringing together the best of both worlds. Okay, let's look at the implementation and you can probably guess it from the title. The object oriented or it's really more from a procedural world, but following the mutable data principle, the approach would here would be you create an empty array at first with the outputs, then you iterate over the inputs that you have and append to your outputs for every for every digit that you have in the input. Works fine. But this one here comes to mind for me. I would have written it shorter, but I didn't have the time. And that is because I find this very easy to write, but it can be a bit tedious to read and reconstruct. So looking at this, what was actually the high level intention, what was meant to be done. And then because you're fairly explicit about the variables and the appending, this can be quite error prone. And if you write this kind of thing a lot, you probably know what I mean. But what's the alternative? And that's a piece that I really like from the functional world. That is the idea of mapping, because what we really wanted to do there was take our inputs and apply this integer function to each element in the input sequence. Now, little disclaimer, int is actually a class that just acts like a function, which kind of just proves the point that Python is multi-paradigm. And I'm just going to leave that aside for now. So what are we doing here? We're mapping, so we're applying the int function over our inputs. And just because, like I said earlier, this is a lazy operation where it is forced to be a tuple, which makes it non-lazy. And then we've got our output values here. If that's a bit unclear, sort of with the order of execution, there is a trick that I like to use from the tools library, which is we can just force this to be left to right with this thread last function, which is essentially creating a pipeline from left to right saying take all raw examples, map it over the int function, and then turn it all into a tuple to collect the outputs. So this is a comparatively concise way of expressing the same thing that we had before. And it's really much closer to the intention, because what we wanted to do was just to apply this function integer to all of our inputs, which is exactly what we're saying here when I'm telling it to loop. I think that makes it really easy to read. You might disagree at this point, which I did as well when I saw it for the very first time. But once you're used to the syntax, this becomes much, much easier to read than those tedious for loops. It can take a little longer to write though, because you have to be a bit clearer about the actual intention. I have to try to abstract things a bit more. But I would argue that's a good thing, and it makes you a better software engineer. So yeah, just as a side note, Python has this great syntax of list comprehensions as well, which is a bit of a mix of both worlds, and it works really well as well. It's kind of easy to read and write, but you really have to be careful. Never use lambda functions inside of list comprehensions. Also never define functions within a for loop, by the way, because those won't behave as you want them to. I'll leave that as it stands. And if you do things like that, just make sure those don't get too long and too complicated, because you do want it to be easy to read. So pull any sort of more intricate logic out into its own function, and then use the function inside the list comprehension or inside your pipeline from earlier. All right. Just another quick example on this, the opposite of parsing, just not quite the opposite. But similarly, when we want to take our internal object and just display it in the object oriented world, we would implement this representation function, which I've done here in this class that I'm loading. And then once we've done that, it's straightforward, we just let this thing display. There's no further thing that we need to do. This is a built in. This is this behaves clever. So that makes it really easy to get a nice representation. The functional way there is a bit more explicit in that we would say, well, to really get this Sudoku all the way, I'm using the pipeline again, we're taking our raw input, we're parsing it to the internal format, then we're formatting it for display, and then we're printing it. I explicitly kept printing outside of there because printing per se is a side effect. So I don't want it in this function to keep this function pure. That being said, the output is kind of the same. Well, it's exactly the same. So the multi paradigm is always, yeah, why don't we just do both or take the best of both? And here, similar approach, just define the function as a pure function reusable, and then enhance your Sudoku class by using that function. So keep the class a bit shallower and keep your sort of reusable chunks of logic outside of it. Okay, that's as much as I want to say about the first part. Let's look a bit more at data structures. Even here, so you see the example going forward. Now we're going to use that Sudoku grid. Up till now, I just modeled it as an array. But let's see what we can actually do. In an object oriented context, you might want to do something like this. You might want to model each sort of small square with a digit as this square class. Then you might want to create an abstract square collection, which just ties a number of these together, typically nine for rows, columns and blocks. But if you want more, well, a Sudoku is really a collection of 81 squares as well, nine by nine. But to make it all nice and explicit, you would say, well, a Sudoku is also really knows its other square collections, it knows its rows, its columns, it blocks. So you've got a very explicit structure there that you can work with. So that gives us this nice instance here where we can say, well, just give me your eighth row or give me a particular square, give me all the information about it. In this case, we know where the square is, what the digit is that is currently filled in, and whether it's locked. So whether this was an input from our original thing or maybe something we filled in later on. Right. So that assumes certain usage patterns because you're putting a lot of context into it. That in turn makes it very intuitive to explore, but it's also fairly rigid, and it requires a lot of boilerplate. Proof is here, the implementation of this was 120 lines just for sort of defining the classes, setups, etc. So what's the alternative? Functional programming is all about simplicity. So here we just say, you know what, this thing is a nine by nine grid. It's a grid of shape nine by nine and of data type int. Done. So three lines instead of 120, very simplistic. We can use it in the same way as before. So after we've parsed our input, we can validate it with this thing. Again, this is a schema is a multi-paradigm language in its own right, which is why we have sort of an instance method here. So you can always do your chain with those as well, as long as they're immutable. Well, we get to that. So this is a very minimalist approach, obviously with zero or close to zero boilerplate. There is absolutely no structure, no context on the data structure itself beyond this is a nine by nine grid, which can make it a bit harder to explore, but it also makes it easier to reuse as I kind of pointed towards before. So what's the best approach? And I can obviously only give my very opinionated answer. But here we go. So why don't we create a Sudoku class that gives the whole thing some context similar like I did it before, but actually underneath that there is just a grid. So we're using the simplicity there. And we don't model all these roles, columns, collections explicitly. That's implementation detail. And we can do that in a very, very elegant simplistic way. But we then do tie it together on this class, maybe have some nice functionality on here and adds context and just brings it all together. I like to call it like a shallow wiring class or anything like that. Yes, I said that here as well. So it saves a lot of code, but it does have the context for the user. And at the same time comes with all the benefits of being able to sort of take small chunks of your logic, take them out, make them reusable and concise. Okay, on to the next part about state handling. Here it's going to be about we have the Sudoku now. We have a grid with parsed it from an input source. But now we want to start filling things in because at the end of the talk, we kind of want to have a solver that automatically solves that Sudoku. So let's think about the next step. How do we fill in the digits? And for this, I'm using a multi-paradigm implementation right away inspired by pandas and it's in place concept because I think that just showcases very nicely what mutable and immutable means and also that it's not necessarily tied to a certain syntax. It's all one class is here. So I'm just going to create a blank Sudoku here, just blank nine by nine grid. So I can show you a few operations on that. The mutable way of interacting with this is change it in place. So I've made it explicit here by saying in place equals true and we're setting at x, y, zero, zero. So at the top left corner, we're setting a seven and then well, the seven is in this Sudoku. So we've changed it in place. That seems natural. Like, I changed it. Now it's changed. It means there is no way back, no history. We're changing everything as we go along. What is the alternative? So the alternative isn't the immutable way of saying in place false. So here we're setting this digit but actually not on this Sudoku. Instead what we're getting back is this new Sudoku here with the digit for set. The big difference probably only shows up when I then go and say wait, but what was the Sudoku? And that's still as it was before where we have the seven filled in, but we don't have the four filled in yet. So we now have two different versions of it and that makes it really easy to use this, parallelize this. It's very efficient, avoids any concurrency errors because you simply have to synchronize less state between any nodes that makes it efficient and also helps with the errors. I could probably give a whole talk on why that's the case, but let's skip that aside. Because you have this before, after picture as well that gives you some natural versioning and also like I've shown before with pipelines, it lends itself very well to that, but it also in this multi-paradigm syntax with classes lends itself very well to method training. That's one thing that I want to highlight is now you can do something like this, say Sudoku, set digit, set another, set another digit and what you're getting back is obviously this change thing with these digits set, but also we've not changed the original one. So you could from there try to solve the whole thing and then go, ah, maybe I did something wrong, backtrack to the original version or to a version before you can save versions. This can be really handy in different application contexts. So my recommendation is I learned a lot from bringing more functional programming into my code and making it by that more multi-paradigm and the idea of immutable data structures really, really helps with a lot of applications I find. So try out things like a frozen data class named tuples which are essentially sort of kind of act like dictionaries and like tuples at the same time, but they're immutable. Frozen dick and frozen dick and persistent map are probably the, the equivalent, yeah, they're probably the same, which is a frozen equivalent of a dictionary. Then we've got, yeah, use multiple data structures in immutable ways as well. So if you have a dictionary, then still maybe don't set things in place, but rather use something from this tools library, which I've mentioned before because it's, it's my favorite library in Python. And there's sort of associate functions, for example, where you have, you give it your input dictionary, a key and a value, and what you get back is a new dictionary with that additional key and value pair set. Also an idea, try to keep functions pure and idempotent, try to pull them out of your classes and make them very reusable and then use classes where configuration and state is more required or desired to wire, wire it all together. Just one more example of how this works in pandas. I'm just going to create a data frame here, just a couple of random numbers, but then I can do a nice method chain here saying assign a new column, assign another new column, based on that one, you work a lot with lambdas on these, and then maybe drop some rows. And that gives me this output here. Don't worry too much about the content. It's just a dummy example. But also we still have the unchanged original data frame. And that I found extremely helpful when dealing with Jupyter notebooks, because people tend to jump around in Jupyter notebooks and not just executed sort of front to back, top down. But if you want to go back and you've changed a data frame somewhere, it can get really messy really quickly. Whereas if you treat your data in a more immutable way, then you can go back and just re-execute any cells and it won't really make a difference. You can jump around more and you have more self-contained pieces of logic, which again makes the whole thing more reusable and because it's more self-contained and more reusable, it's also closer to production ready. I've noticed this a lot. We've, well, I've talked to the wider data science team and got them to basically write all the Jupyter notebooks for new algorithms, new data analysis pieces and that more in this immutable style. And since then my job got really a lot easier off taking these things and putting them into a production system because it's just more things that are easier to take out and use. Okay. Last main part is how do we deal with multiple implementations? And here we're going to actually look at now solving the Sudoku. So one thing that I thought about in solving this was a deterministic solver based on a mask. So we would create a mask that only shows you the fields where you could put a digit in. So where could you in theory you put this digit and that per digit? So you create these masks, then fill any unambiguous ones. So where there's in the mask just one possible place where digit could be within its row column or block. And then you just keep repeating that. That alone works well for very easy Sudoku that have a clear solution, but it's actually insufficient when you get to the harder ones because they can have multiple solutions. So a deterministic solution is just not going to get you there fully. So I created a random solution as well. Also uses the same concept of a mask because we want to still solve it by the rules, right? But then we just fill a random digit and we just repeat that and keep doing it. The problem is that we often back ourselves into a corner with that approach. So we have to backtrack and rerun the whole thing. And you have to rerun it so often it makes it prohibitively slow. I think I tried with 100,000 runs that was definitely not enough and the million rows took million tries took too long for me to for my patients. So what I did instead was bring it together into a combined approach. And here we're just saying run it deterministic as long as you can. And once you sort of run out of moves, then try a random step and go back to deterministic and just keep iterating that. You might still need a few tries because of the randomness, but this is actually a really, really effective solution. It's still fairly simple, but it's an effective solution. The big question is how do we get those together? So how do we organize our code to reflect that? Let's look at the object oriented way first again. So you would probably create something like this, a hierarchy of solvers. So there's the abstract idea of a solver at the top. Then we probably want something like a step-based solver that just sort of handles iteration, retrying and running steps again and again, that sort of thing. Then one implementation of that or one sort of abstract subclass of that is a mask-based solver so that implements our logic of creating a mask and then running the step. So it kind of is a mask-based step-based solver really. So then our actual implementations of deterministic and random solver can just be underneath and those essentially now just implement the step that is taken. And then the combination would just use those two classes so it knows those mask-based solvers and combines them. A solution could look something like this or the use case could look something like this. We instantiate our Sudoku as we had it before. We instantiate a solver. We tell the solver to solve the Sudoku that we gave it and then we have a look at the solution and there it is. Let's try the combined one. Yeah, works the same. Glad. So what is the idea of that? We have a mutable data access, similar issues as before. It can make things a tiny bit faster but it's usually not worth it unless you're really in a performance critical environment and even then the difference can be minute. I do find that when you organize your code in a way like this, you end up with a lot of single method classes because it kind of just has to split the functionalities apart in order to be able to reuse them. But if you have a single method class, why is it not just a function? I struggle to understand the logic there because all you're doing is you're adding boiler plate. So it seems to me just a complicated design for simple functionality but again, similar things apply as before. It does have some use for the user because it's a straightforward way of using it. So what's just a word count for proof that it has a bit of boiler plate. What's the functional approach to that? Well we just create these functions and then sort of worry about wiring them up later. We have a function that creates a mask. We have these individual step functions, the combined step function. I forgot the arrows there actually uses the random and deterministic steps. And we've got these solve function that iterates over them. So kind of similar idea of splitting the functionality up but just broken down into the actual pieces of logic. The use case looks a bit different and you kind of need more context there to be able to run this. So we can wire, pre-wire a function called solve combined by saying it's solved with this combined step and then we can run it like we did before with our pipeline taking a raw example, parse it, solve it, format it, print it and there we go. Same output and output. I think some feedback here at the talk back channel. Okay, brilliant, thank you. So let's see. So this makes the responsibilities very clear per function. It's a very simple pragmatic design. Once you know how to use it, it's very easy to introspect and combine in different ways as well and it's much more concise. The code itself is more concise and on top of that we don't actually have any of the base module with the boilerplate code so we're really saving a lot of code writing here. But like I said, you have to know a bit more about the library to do your wiring. So where do we go from that? What's the multi-paradigm solution? And here you could opt for a class like we've done before to do the wiring but I kind of just wanted to showcase that you can do it in a slightly different way, this time leaning a bit more to the functional side than the object side to do this. And that is I've created a solving function here that does the wiring mainly in a functional way. But we're still using the Sudoku, the higher context class to represent the actual Sudoku, what we're running this as solving function over it. So we've basically flipped it around rather than putting a pure function on a class, we're now using the class as an input and the pure function is the sort of top level entry point. And yeah, take my word for it, this runs perfectly fine. So this brings together the simplicity and clarity of the functional programming, but it also makes sure you have all the high context of a Sudoku that makes it really easy to explore the actual Sudoku itself. And then you just want to solve with the input of that class you've prepared. If you really want, you can sort of create, I'd call this a solving configuration, where you say we've got a step function here, we've got a maximum number of tries, and then we're just going to use the solve function that I just showed you and put it on a class. So I'm making this callable here as well. So I wouldn't necessarily recommend this, I just want to showcase here that you can sort of make the lines blur between what's a function and what's a class with this callable syntax. And here we can say, well, take our raw example, this time parse it with the from string method on our multi-paradigm class, and then thread this class into a solving configuration using the combined step and trying a maximum number of 100 times. So that works perfectly fine. And like I said, this is just now giving you some ideas on how you can combine things together. Right, that's the main part. I just want to give some key takeaways and then open up for questions. I'm going to do this fairly quickly though, because we're running short on time. So object orientation, just some observations. It's typically your fairly top-down design, you create larger, very topical structures and you're quite explicit about high context. You bring functionality and data together in a topical way. It leads to very intuitive use case that makes it all very explorable. Functional programming is very much the other way around, going more in a bottom-up fashion, tries to simplify everything as much as possible and put it into small chunks of things that you can actually reuse, functions that you can reuse, and then are entirely separate from the data. So there's a high isolation and we have a low context. That typically leads to very reusable things. Tidy and concise code, I find if you do it right. And the use cases are a bit more flexible. Just a quick side note, to keep your code tidy and concise, you do need to work a bit more with modules than you would have to in a purely object-oriented way. But there's enough means and ways to structure your code. So what does that mean for multi-paradigm? It's a pick and mix of both worlds. Warning, you can pick and mix the worst of both worlds. Try not to do that. I think I've shown you probably some useful ways of combining. Use pure functions in a mutable context. Just bring that all together. Good thing is, no side effects, no problems. You can always use the pure functions in a mutable context and you shouldn't get any problems. The other way around when you use something that works with mutable data in an immutable context, then you want to make sure you're explicit about it and use a copy and modify pattern. Just to make sure you're not changing your inputs and by that, keep it immutable for the context that is required. So ideally, you end up with both intuitive and flexible use cases and you end up with both something that's explorable and has very reusable components. We have four minutes left. Okay, perfect. That is including Q&A. So if you want to do Q&A, then we should switch to the next one. Okay, thanks Mark. Yeah, so a favorite approach, iterate with a REPL. That's where I'm going to wrap up. So we have some time for questions. Okay, excellent. Thank you very much, Elias. That was a very, very interesting talk. Sorry, I had to cut you a bit short. We do have a couple of questions. We don't have, we have three minutes left for questions, so I'm just going to read them from top to bottom. So first question there, how do you recommend organizing your helper functions in a module? That depends very much on what the context is about. So I would generally start writing them in one module, whatever you use them for, and then sort of start breaking them out when you realize, hey, this is something that I could reuse, maybe put it into a utility library or into a topical library or even just in a sub module, pull them out when it starts to look like, hey, this is not just for this particular use case. This could or will be more useful in others as well. Okay, great. Thanks. Next question is why data class and not named tuple? I use data classes here to just short, get rid of some boilerplate. I showed you the 120 lines that was already with using data classes. So I used them mostly for the object-oriented context here and then just kept going to keep it sort of similar in the talk. Frozen data classes and named tuples, really, if you just use them this way, there is no considerable difference. I've definitely used named tuples as well with some functionality on them. Okay, great. So one more question. Why should you not use lambda inside list current prehensions just for readability? No, not just for a readability. There is a, I don't know what the correct term there is, but there is a problem with context generally that a for loop doesn't have a context. So if you end up, for example, defining slight tangent, if you define a function inside a for loop and then you sort of memorize that function for later use, you're actually going to override the function and only be left with the one of the last iteration of the loop. So you can really get yourself into some unexpected behavior there. And that same principle translates to lambda functions inside a loop. So if you directly execute something in a list comprehension, you don't actually need the lambda, but as soon as you start to do something, say with pandas inside a loop with a lambda df and then use something on the data frame, it will not do what you wanted to do. So just try to avoid it. Okay, thank you very much. I think that was the last question that we can take. I would like to ask the attendees who had more questions to go to the talk channel that we have for this talk, which is talk write multi paradigm code. And then you can ask additional questions there and Elias can then answer those. So thank you very much Elias for the nice session. Let me try to run a short applause for you.
and functional programming together and how to learnings from bringing those together and what I implementations for sort of the same concept, the object oriented side. Python is very pragmatic, is those two paradigms are concepts. They're not Now with that as a background, let's dive right is sort of compare the OO and the FP side to one together the best of both worlds. Okay, let's of both worlds, and it works really well as well. makes it very intuitive to explore, but it's also this is a schema is a multi-paradigm language in structure, no context on the data structure can save versions. This can be really handy in data frame somewhere, it can get really messy issues as before. It can make things a tiny bit able to reuse them. But if you have a single use the pure functions in a mutable context and we have for this talk, which is talk write multi