We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

How to write multi-paradigm code

00:00

Formal Metadata

Title
How to write multi-paradigm code
Subtitle
... without making a mess
Title of Series
Number of Parts
130
Author
License
CC Attribution - NonCommercial - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Identifiers
Publisher
Release Date
Language

Content Metadata

Subject Area
Genre
Abstract
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.
61
Thumbnail
26:38
95
106
Machine learningFunctional programmingMachine codeVirtual machineDisk read-and-write headOrientation (vector space)TouchscreenSheaf (mathematics)Meeting/Interview
Programming paradigmComputer programmingMultiplicationMachine learningComputing platformDisintegrationTexture mappingContent (media)Machine codeData structureQuantum stateJava appletAlgebraic closureObject (grammar)Physical systemHierarchyInheritance (object-oriented programming)AbstractionEncapsulation (object-oriented programming)Polymorphism (materials science)Functional programmingType theoryFunction (mathematics)Performance appraisalSound effectIdempotentNumberNumerical digitContext awarenessParsingArray data structureString (computer science)Factory (trading post)Functional programmingEnterprise architectureJava appletVirtual machineBitPrincipal idealLine (geometry)Dependent and independent variablesCASE <Informatik>Data integrityProgramming paradigmQuicksortPoint (geometry)Object-oriented programmingSocial classImplementationView (database)Type theoryMachine codeIntegrated development environmentRight angleWritingSet (mathematics)DataflowoutputProgramming languageProcedural programmingArithmetic meanAlgebraic closureHierarchyInheritance (object-oriented programming)Data typeOrientation (vector space)Function (mathematics)Block (periodic table)MereologyNumberAlgorithmProcess (computing)Degree (graph theory)Variable (mathematics)Physical systemQuantum stateSound effectRow (database)Open setWebsiteData structureAbstractionMachine learningEncapsulation (object-oriented programming)Polymorphism (materials science)Core dumpString (computer science)DigitizingFactory (trading post)Presentation of a groupSlide ruleContext awarenessSoftware repositoryInstance (computer science)Array data structureComputer programmingComputing platformINTEGRALComputer fileDifferent (Kate Ryan album)Surjective functionDot productObject (grammar)Operator (mathematics)Computer clusterComputer animation
Programming paradigmMultiplicationContext awarenessFunction (mathematics)Machine codeLoop (music)Lambda calculusElectronic visual displayFile formatObject (grammar)Functional programmingData structureSocial classHierarchyBlock (periodic table)Square numberMachine codeLoop (music)Functional programmingObject (grammar)Social classQuicksortElectronic visual displayFile formatCASE <Informatik>outputSequenceContext awarenessInterior (topology)TupleLibrary (computing)MereologyBitLambda calculusMultiplication signExterior algebraIntegerElement (mathematics)Function (mathematics)Matrix (mathematics)Order (biology)ImplementationLogicPoint (geometry)NumberOperator (mathematics)DigitizingAbstractionRepresentation (politics)Software engineeringThread (computing)Electronic mailing listMixed realityArray data structureMappingSquare numberSound effectProgramming paradigmData structureProgrammschleifeView (database)Right angleStructural loadBlock (periodic table)Row (database)Variable (mathematics)Error messageWritingMultiplicationRaw image formatLevel (video gaming)Computer animation
Square numberSocial classHierarchyBlock (periodic table)Boilerplate (text)8 (number)Type theoryData structureStrutMultiplicationProgramming paradigmMachine codeContext awarenessQuantum stateNumerical digitImplementationParallel computingChainingFunction (mathematics)IdempotentConfiguration spaceTupleQuicksortMereologyPattern languageoutputSource codeProof theoryArithmetic meanRevision controlCartesian coordinate systemData typeLine (geometry)Data structureExterior algebraProgramming paradigmBoilerplate (text)DigitizingFunctional programmingSocial classImplementationTupleContext awarenessMultiplication signSquare numberRow (database)Machine codeInstance (computer science)MathematicsInformationWave packetBacktrackingCASE <Informatik>Programming languageRight angleChainingSimilarity (geometry)BitEndliche ModelltheorieQuantum stateBlock (periodic table)Parallel portLevel (video gaming)Concurrency (computer science)Error messageSet (mathematics)Shape (magazine)Interior (topology)Different (Kate Ryan album)LogicData dictionaryMultiplicationOperator (mathematics)Point (geometry)Equivalence relationComputer animation
Data structureFunction (mathematics)IdempotentSocial classConfiguration spaceTuplePolymorphism (materials science)Different (Kate Ryan album)Random numberDeterminismHierarchySingle-precision floating-point formatBoilerplate (text)Key (cryptography)AdditionFrame problemQuicksortData structureFunction (mathematics)Functional programmingRandomizationQuantum stateLaptopLibrary (computing)DeterminismSocial classHierarchyImplementationLogicBookmark (World Wide Web)Similarity (geometry)CASE <Informatik>Auditory maskingDigitizingMessage passingBitDifferent (Kate Ryan album)Block (periodic table)Lambda calculus1 (number)Integrated development environmentoutputOrder (biology)Associative propertyRow (database)Data dictionaryRule of inferenceSingle-precision floating-point formatIterationProduct (business)AbstractionMusical ensembleEquivalence relationMereologyCombinational logicField (computer science)TheorySound effectRandom number generationConfiguration spaceMachine codeData analysisProcess (computing)Cellular automatonPhysical systemAlgorithmContent (media)Object (grammar)ChainingBoilerplate (text)Computer animation
Single-precision floating-point formatSocial classBoilerplate (text)Function (mathematics)EmulatorModule (mathematics)Object (grammar)Context awarenessOrientation (vector space)Data structurePersonal digital assistantComputer programmingLogicMachine codeMultiplicationProgramming paradigmSound effectType theoryLibrary (computing)Functional programmingProgramming paradigmPattern languageMixed realityConnectivity (graph theory)Data miningPerfect groupString (computer science)IterationMultiplication signSocial classContext awarenessLogicCASE <Informatik>Greatest elementTheory of everythingBitArithmetic meanModule (mathematics)Image resolutionBoilerplate (text)Machine codeRaw image formatFunction (mathematics)FeedbackMultiplicationDependent and independent variablesAuditory maskingObject (grammar)Configuration spaceSound effectWordProof theoryCountingQuicksortCombinational logicSystem callParsingFile formatMaxima and minimaoutputPoint (geometry)NumberMereologyState observerArrow of timeSimilarity (geometry)Heegaard splittingMusical ensembleObject-oriented programmingLine (geometry)Library (computing)Thread (computing)Letterpress printing
Machine codeLine (geometry)TupleSimilarity (geometry)Different (Kate Ryan album)Lambda calculusElectronic mailing listFunctional programmingLoop (music)Term (mathematics)QuicksortMachine codeContext awarenessSocial classModule (mathematics)Object (grammar)Library (computing)MultilaterationTangentIterationBitWritingProgramming paradigmUtility softwareCASE <Informatik>Boilerplate (text)MultiplicationComputer animationMeeting/Interview
Transcript: English(auto-generated)
Okay, so now we have ten o'clock and we can start with the next session. We have Elias Misla, who is going to join us and give a talk about how to write multi-paradigm code without making a mess.
And Elias is the principal machine learning engineer from Previs, 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. 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, just going to say, I've got an introduction here, basically already said,
Previs, 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 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. And 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, which basically said it to a degree already, Python itself is a multi-paradigm language.
Unlike, I mentioned closure as 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 function 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, a 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 block, 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.
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 a 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. Everyone seeing this kind of thing knows how to use it, how to even start
it, because most IDEs 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 a 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 digit that you have in the input.
Works fine. This one here comes to mind for me. I would have written it shorter, but I didn't have the time. 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, a 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, but I'm just going to leave that aside for now. So what are we doing here? We're mapping, so we're applying this int, I'm just going to call it function, we're mapping the int function over our inputs.
And just because, like I said earlier, this is a lazy operation, we just force it 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 in 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 our 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, we're not 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. Right. Just another quick example on this. The opposite of pausing just for not quite the opposite. But similarly, when we want to take our internal object and just
displaying 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, and 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 pausing 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, 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, and here, so you see the example. So, going forward. Now we're going to use that Sudoku grid. Up till now I just modeled it as a, 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, Sudoku is really a collection of at one squared as well, nine by nine. But to make it all nice and explicit, you would say, well, Sudoku is also really knows it's other square collections
and knows it's rows it's columns, it blocks, so you've, you've got a very explicit structure there that you can work with. So, that gives us this nice instance here that 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 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.
And 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 in. So three lines instead of 120 very simplistic. We can use it in the same
way as before so after we've passed our input, we can validate it with this thing. Again, this is a multi schema is a multi paradigm language in its own right which is why we have sort of a 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 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, but 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.
Before, but actually underneath that there is just the 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, 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, we've passed 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 classes 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, done, dusted. So we've changed it in place, that seems natural, like, duh, 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 is the immutable way of saying in place false. So here we're saying 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 four 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 keep 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 chaining. And 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 Dict and Persistent Map are probably the equivalent, yeah, they're probably the same, which is a frozen equivalent of a dictionary. Then we've got, yeah, use mutable 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 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 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. 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 of 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 this 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 it's 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 is in the mask just one possible place where a digit could be within its row, column or block.
And then you just keep repeating that. That alone works well for very easy Sudokus 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, I tried with 100,000 runs that was definitely not enough, and the million rows took a 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 on the, let's look at the object oriented way first again. So it would probably create something like this 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 sub class 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.
And then the combination would just use those two classes so it has a, it knows those mask based solvers and combines them.
I'm a solution could some look something like this, or the use case could look something like this we instantiate our Sudoku as we added before we instantiate solver, we tell the solver to solve the Sudoku that 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. Yeah, what, what is the idea of that. We have a mutable data access similar issues, as before, it does, 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, 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, I struggle to understand the logic there, because all you're doing is you're adding boilerplate.
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, it's a straightforward way of using it.
So what's, what's just a word count for proof that it has a bit of boilerplate. 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 the 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, of logic. The use case looks a bit different and you kind of need more context there to be able to run this. Um, 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 pass it, solve it formatted printed. And then we go, same output and output.
Getting some feedback here. Talk back channel. Okay, brilliant. Thank you. So, um, 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 at 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.
Like I said, it's, 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 I, 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 your object side to do this. And that is, I'm create, I've created a solving function here that does the wiring, mainly in a functional way, but we're
still using the Sudoku that the higher context class to represent the actual Sudoku what we're running this solving function over it. So, we've basically flipped it around rather than putting a pure function on a class, we're now using.
We're 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 this 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 call 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 the function and what's the class with this callable syntax. And here we can say, well, take our raw example, this time parse it with the fromString method on our multi-paradigm
class, and then thread this class into a solving configuration using the combine step and trying a maximum number of 100 times. Also, 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 parts, 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 a 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 that 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. But I think I've shown you probably some useful ways of combining. Use pure functions in a mutable context. So 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.
So 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.
Yeah, I like to... We have four minutes left. Okay, perfect. My... Including Q&A, so if you want to do Q&A, then we should switch to... Okay, thanks, Mark. Yeah, so favorite approach, iterate with a REPL. And 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. No problem. 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. 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 name tuple? I use data classes here to just get rid of some boilerplate.
I showed you the 120 lines that was already with using data classes. So I use 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 name tuples, really, if you just use them this way, there is no considerable difference.
I've definitely used name tuples as well with some functionality on them. Okay, great. One more question. Why should you not use Lambda inside list comprehensions just for readability?
No, not just for 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 if you end up, for example, defining a 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 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 if you directly execute something in a list comprehension, you don't actually need a Lambda. If you use something on the data frame, it will not do what you want it 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.