 Okay, so my name is Luca Jakobowitz, and this is Functional Conf, as you may all know, and my talk is going to be about testing in the world of functional programming. Just to preface a bit, it's going to be a bit opinionated. It's also going to be, it's also going to represent a lot of the problems that we faced in our team. It might not always completely apply to everyone, but these are just some of the lessons I learned for about a year or so doing purely functional programming in production. So without further ado, a bit about me. So as I said, my name is Luca Jakobowitz. I'm a software developer at CodeCentric in Germany. I also co-organize some meetups, Scala and Idris meetup in Düsseldorf. I also maintain some open-source libraries such as various cats libraries, cats, cats effect, catsMTL, cats tag loss. If you have any questions or anything or ideas about those, come to me. I also maintain this library called Outwatch. And I'm also super enthusiastic about functional programming, which is the whole reason I'm here in India. Okay, so let's look at the agenda real quick. I'm going to talk about only three things. Actually, it's just two things in conclusion. So the first thing I really want to talk about is property-based testing, because property-based testing really helps getting more confidence in your tests. And then the second thing is about mocking. Mocking in functional programming. It's a bit different from the way you might want to mock you in Java, so we'll have an in-depth look about that. Okay, and at the end we'll just try to find some conclusions. Okay, so property-based testing is really, really awesome. So who here knows about property-based testing? Okay, that's quite a few. So in essence, property-based testing lets you generate test cases, right? And that means that you can no longer, like it takes away your ability to cheat. And that means that if you write a function and you want to test that, you give this function some inputs and you have some expected output, right? But if you don't use property-based testing, if you just test manually, you can kind of cheat by just choosing just the right inputs where this function works perfectly. And even if you don't cheat, you might have some edge cases where, okay, you know about these edge cases of your function, so you manually test these edge cases. But we all know that over time and over a lot of refactorings, things in your code base tend to change a lot. So these edge cases, they're not always, they don't always stay the same, right? So over time, your edge cases might change a bit or they even, they might get more edge cases. So, and of course, we as programmers, if we change something in the code, we should always change it to reflect in the tests as well. But as a lot of you may know that sometimes we tend to forget or we tend to overlook certain things. So property-based testing takes away that so you can no longer manually input any, you can no longer manually write the inputs to your function. So you just let the property-based testing framework generate a bunch of them and that helps us find bugs as early as possible because we don't have to think about what are the edge cases because the framework just says, hey, you cheated here, write a function that works for all of our input cases. And this is where some of the problems are. And the next I'm going to talk about some of the pains where property-based testing can be a bit difficult. So the first thing is, have you ever seen a function like this? So this function called UUID created after, since Gala, in case you don't know it. It takes a UUID as a string and a date as a string and then returns a boolean if the UUID was created after that date. And there's this nice comment that says, oh, okay, this should use UUID version one and also the date should be in this format. And if you try to write a property-based test for this you'll probably run into errors real quick that say, hey, this function doesn't work for any strings. So we're kind of lying like this function doesn't really set out the requirements, right? The inputs to this function should not be two strings because strings could represent nearly everything. What we want is a UUID and a date. And so what can we do about that? So how can we kind of fix this problem? And one thing you can do is you can use new types. So what are new types? New types basically say, okay, instead of using the primitive types like string and integer, which have enormous amounts of values that it can possibly have, right? For example, the state that we saw earlier could also just be a string of Chinese characters which usually doesn't parse very well as a date. So a new type basically says, hey, let's constrain this type or can new types can say, okay, we want only a subset of the possible values a certain type can have, right? So string can have a lot of values, but for dates we only want maybe a subset of that or we want to encode it differently altogether. So and there are some common reasons against new typing. In Scala, they are usually written as anyval classes. So value classes, if you've seen those before. And so let's just look at the common reasons why you wouldn't want to use them and let's try to work against them and see how we can address them one by one. So first one is it won't support the operations I need. So for example, if I new type a number to, for example, use a rupees, for example, now I can no longer add two values of this together and I can't say, oh, I just want maybe like $5 plus, $5 should be $10, but now I don't have this plus operation anymore. So it can get quite annoying and that feeds into the second reason, which is it will lead to a lot of boilerplate conversions where, oh, okay, now in order to add these two values together, I need to convert them to int and then afterwards I have to convert them back and now I've written a lot of things that just seem a lot of boilerplating. And of course, what another problem is is that it can give us a performance penalty, right? So sometimes, especially in Scala, many VAL classes, when we new type something, it will automatically box and unbox whenever we add it, for example, to a list or we use it inside of an option and in general, this can lead to a lot of allocations and allocations, which of course leads to more memory to a higher GC load and gives us a general performance penalty. So how can we deal with this? So let's have a look. There's a really nice library in Scala, that's called Scala new type and it basically takes away most of these problems. It kind of also gives us a glimpse into Scala's future because I think most of the features Scala new type has will just be like features in I think Scala three, if everything goes well. So let's have a look. So first thing we do is we just write a case class and say, hey, let's add this new type annotation and now we have the euros new type that basically at runtime is just going to be an integer. So this euros gets completely eliminated and unlike value classes, it will never box. So it will always be this Java lang integer. And what we can do with this, also what else this gives us is type class automatic type class derivation. So if this type that we're wrapping in a new type, this integer has certain type class instances, we can also get the same type class instances for our euros class. So for example, if you wanted to do something like this where we'd say, okay, I want, because euros are fundamentally they're numeric, right? I can add them, I can subtract them. This means basically that what we can do is say, okay, we can define a numeric instance for euros just by deriving it from the int. And hopefully this is something that will come to Scala 3. So in the future, you might see more of this if you're doing a lot of Scala. And what we can do then just by using the syntax defined for numeric is say, okay, well, let's just add these up and this plus will just use the numeric syntax and this will just work. So this is all the code you basically need to get this compiled. And now you don't have to convert from one to another and it makes a lot of things a lot nicer. Okay, so another real cool thing that I wanna show you is this library called refined. And refined basically gives you the ability to limit the amount of values, the set of values any given type can have. So let's look at an example. So let's say I want a string that represents some URL. Usually when I just say a string, I can write anything and it's easy to get it wrong. You might get it from some user input and he might have done something wrong. So you have to clean up his input. So what you can do is you can use the string refined URL type. And this will at compile time check if this literal right here is a valid URL. And if it is, it will compile everything's fine. And if it's not, it will say, okay, I can't do that. So for example, something like this where we got the wrong protocol, we use HRRP which is probably a typo. At compile time, it will say, hey, this URL predicate failed. I don't know if this protocol HRRP. And this is really cool. And what we can do as well is do things like int refined positive where we say, okay, we only want a positive integer. There's tons of other different refinements in refined. And what we see here is this static refinement where we refine literals. But usually for non literals, right? So when we get a value from user input a string that's supposed to be a URL, we can't say, oh, okay, let's just make it. Like we can't check it at compile time because we don't have the value at compile time. We only get the value at runtime. So what we have to do is say, okay, let's refine this. And then what we get is an either an error or the valid refined URL string, right? So this is basically how we do it. So, and this gives us the best of both worlds for literals, we can just say, oh, okay, you can check this at compile time so you know it's a refined URL. And for things where we don't have any control over for things that we have to get at runtime, now you can say, okay, just give me back an either where the left case is the error and the right case is the value that we actually wanted to refine. Okay, and what's really cool about this is that with ScalaCheck, which is one of the most widely used Scala property-based testing framework, these two, they have a module called refined ScalaCheck that defines generators for all of the refined types. So what we could do, for example, we need to just import this and then if we have this left pad function, which pads a string to the left and adds just space characters. And what we do is we say, okay, for any string and for any int refined positive, that means that we can't give it a negative integer because it doesn't make sense to pad a string with negative amount of characters, right? So we want a positive integer. And I'm not gonna write the implementation. What we can then say, and this is the ScalaCheck syntax basically, we can say for all S, which is a string in N, which is a positive integer, we can say, okay, for any, if we left pad that string with N, that length should be either equal or larger to N, right? So if the string was already larger than N, then of course the padded one will also be larger. But if the string was smaller, then the padding will also make it larger. So what we get is a property-based test that will actually check all of these values correctly. And if we just used int here, we might get some minus, we might get some negative numbers and then this test kind of would fail, right? And this kind of gives us back the confidence that, okay, this function will work as expected. Yes, so exactly, that's a good point. Sorry I didn't explain that. So yes, this is going to, depending on your configuration, but usually this is going to generate about a hundred different string integer pairs. And then we'll run this function with that. Okay, so what I'm trying to tell you here is that whenever you only accept valid inputs for your functions, that doesn't just make it easier to property-based test, but it also makes your code a lot more precise because you can no longer call these functions with values that it doesn't accept. So you kind of restrict the input values for your function and that makes it a lot easier, not just to property-based test, but also to think about what your function could actually be doing. So, and there are a lot of more of these different kind of gems out there. So Refine has a lot of these things, but there's also some other really cool libraries. So let's see. For example, what we can do is, if we use a non-empty list, and sometimes you might want to aggregate values that are in a non-empty list and then divide by the size to get the average, right? And the problem here is, can be that because if you look at the size function or the length function of lists, a non-empty list, they actually return an integer, and an integer is not guaranteed to be positive. But a non-empty list always has positive integers as its length, right? So a non-empty list can never be have length zero because it wouldn't be non-empty. And with Refine, you get this Refine size method, which will give you an Intrefine positive. And now you have the guarantee that when you're, for example, calculating the average, you're going to divide by a positive number. And that, well, can maybe after some refactorings, if you don't do this, it can lead to some division by zero errors that happen to us. So we introduced this and now it's all smooth sailing, no more runtime errors, it's really nice. And for those that like to do validation using cats validated instead of either, there's also something like this where you can say, hey, I want to use a validate function instead of refining to either. And then you get a validated NEL of the errors and the valid SHA1, right? And that is just, well, SHA1 hash. And it just checks if it's valid. Okay. And in general, I've tried to come up with a few examples of this and what you can use instead. So if you use a duration of time, if you need a duration of time, Scala has this neat type called finite duration and also duration for infinite durations. And you can use that instead of using just integer because it gives you the units. And for example, one error that can occur is if you have one duration in seconds and you have another duration of milliseconds and now you forget that there are different types. So you add them up and then you have a problem. So with finite duration, this things can't happen because the actual duration is stored along with the actual duration type. So it's at a one second, one millisecond and then you can just go from there. For non-empty sequences, you should be using non-empty list because it gives you a lot of different guarantees. You can now call it something like reduce on it safely and also maximum and minimum because maximum, minimum usually don't make any sense if you have an empty list. For dates, please don't use strings, use something like local date, which was introduced in Java 8 and I think everyone should be using Java 8. Almost everyone should be using Java 8. Is anyone not using Java 8 yet or something earlier? No, perfect, cool. So use this local date, it's nice. It's a lot better than Java SQL date or Java Uchill date. If you wanna save an IP address or want to take an IP address, please don't use a string, just use something like a string refined IPv4 or IPv6, if that's what you need. And you get the idea, right? So try to use these strong types that give you guarantees that your data is actually valid. So whenever you try to write a function and then try to write a test for it, you might see that, oh man, I've got a lot of these cases where a string just doesn't cut it. And so you can check something like refined to see if there's something out there and usually there is because there's only so many cases you might have. And if there's not, you can always model it yourself, for example, using like a case glass, a seal trade, something like that in general. And there's really, really cool other libraries. I've hinted at this before. And one of these is called Libra, which is a dimensional analysis library and it gives us these different unit of measures. So for example, we can multiply two meter values and then instead of receiving the same meter value, we actually get a square meter value and then we can divide that by another meter value and then we get back a meter value. So it actually knows what these multiplication and division operators are doing. And it's really nice. And FUUID is a functional UUID library where usually when you create a new random UUID, yeah, this is usually considered a side effect because it won't, it's not referentially transparent because every time you call the function, it will give you a different result so it's not deterministic. And if you wanna create one from string, you can just do that. You can just UUID dot from string and it will throw an exception. Usually that's not what you want. So FUID fixes that by just returning random UUID values in IO and it'll use either for constructing a UUID from a string. And squants, which is very similar to Libra, but also comes with a lot of built-in units of measure. So you have stuff like kilowatts and tons and it also knows about these different things. So for example, if you would say, oh, I have five kilowatts and then I want to divide that by a certain amount of money to get kilowatts. How many kilowatts can I get for your money? And then you might want to multiply that by some other measure and you can just go crazy with this and it's really nice. If you actually, if you work with any of these units of measures, I really, and of course, if you work with Scala, I really recommend checking either Libra or Squants out. They're both really nice. So we learned about property-based testing and strong types and how they help. So how do I go about testing something like this? So we have the send XML function, which takes a string, parses it like as some XML and then just sends it to some external server. We can't really see. And then if we have a list of like some XML values, we want to use, we want to send them all to this server by using the send all function, which is just calling for each and then the send XML. So there's a few, few different things that might be wrong here. So the first thing we can do is say, okay, let's split it up. And instead of using, instead of using a string, we can first parse it to some XML value. We say, okay, let's parse it. And then we can also say, okay, let's send it. And instead of just returning unit, we'll just return IO unit to regain referential transparency. And now we have these two functions and the first function we can really easily test by saying, okay, we'll give it some actual XML as a string. And then it should parse it correctly. And if not, it should just return the left side a throwable, some kind of exception. So this is nice. And then of course we can combine them to create this parse and send function where we just lift the parse XML, which returns an either into IO using this IO from either method and then flat map it using send XML to get to just send it then ask afterwards. So this works. And of course, if we have a list of these, we can just use the traverse function to do that for any number of lists, any number of elements. So we can test parsing, that's easy, but what about sending? Like how do we test the simple line of code? And I think that's one of the most difficult questions in all of software. Like what do you have side effects that use some external service over which you have zero control? How do you test that? And yeah, it's difficult. So testing what we don't control. Here we kind of have like two options that are not mutually exclusive. And the one option, the simple option is just to say, okay, let's say we have like five systems that all talk to each other somehow and let's fire them all up and then test everything to see if it works, right? And that's integration testing. But that can be difficult because you no longer have the ability to introspect each single little thing. And integration testing is like, it's all its own talk basically because there's so many things you need to think about. So what I'm going to be talking about here is the second option which basically talks about marking. And we separate ourselves from the outside world and try to pull in all of the logic by using marking. And this can be easier because you no longer have to talk to outside services. You no longer have to be sure that all of the outside services are actually running. But of course it's potentially a lot less accurate because you can no longer be sure that the marks you're creating or the marks that you're testing are going to behave in the same way that the real service is actually going to do. So I'm going to take a quick drink and then we're going to talk about how we use marking to test it. Okay, so let's say we have, let's say we want to test some sort of code that does a lot of different things. So it might want to log something here. It might want to call into some service here. And there's a lot of different things going on. We want to write something to a file server. And the best thing to do first is to basically disentangle the different kind of types of side effects we want to do. So, and what's best for that is really using this thing called Tagless Final. And Tagless Final, if you haven't heard about it, basically allows you to separate a problem description from the problem implementation from the solution. So that's what I just said. And this means we can define our own algebras and those define our interactions. So for example, if you want to talk to a mail service, we say let's define an algebra that says how do we interact with this mail service? And if we want to talk to some file service, we can define an algebra that defines how we interact with this file service. And this gives us like an extra level of extraction, but also it allows us to maintain flexibility because these algebras are very abstract, right? And, but we, in order to actually implement the code that will be run, we will have, we can provide what we call these interpreters. And we can define a lot of different interpreters for the same algebra. And so for example, for, if you want to run things in production, we have a production interpreter. And if you want to do something in testing, we can have a testing interpreter. And this gives us the flexibility to disentangle these different things. So how to do this? So let's say we want to create, we want to get started with the titles final. So first we model algebras just as scale traits parameterized with a type constructor. And that's where kind of the effect things going on. And then we can create a program simply by using a method and then we constrain this type parameter. For example, with the modad, I said the emirate, I'm so sorry. And then interpreters are simply just implementations of these traits. So let's have a look. So this is the code that we have right now. We want to book a few things. We want to book a drink, a Coke. We want to book some womb service. We want to book sandwich, which should be whole wheat and have hummus. Hummus is great. And now we want to convert this into titles final and actually be able to test it. So what we do is we create this booking service algebra, right? That's parameterized by this type constructor F. We say, okay, we can book drinks. We can book other things. So the first thing is we define this book drink function which takes a beverage and doesn't return anything because it just goes there and returns it. Returns nothing because, okay, we'll just, the string will come up in some time. This is just the booking service. It doesn't actually concern how to get that drink to you. And then we can book some room service and also a sandwich, which way you want to specify the dough and also the topping. So this is very simple, most simple as it gets. And now what we can do is we can say, let's define, we define our book things function from before. And we'll, instead of using this for comprehension since we didn't really need it, we can just use this apply write shark function which says, okay, one this and then one the other things. So we book the code, we book room service and then we book our whole wheat hummus sandwich. So this is basically equivalent to the function we saw before just expressed using our booking service algebra. Sorry. This write shark operator. Okay, so basically it uses the apply type class which says that you can run two things in parallel and say, and do something with the result of both. So this just says, okay, run this, run this and then run the other thing, basically. That's the simplest way to talk about it. There's lots more documentation on this. And the cat's website, which I recommend. Of course I'm biased, but I still recommend it. And yes, okay. So let's look at a possible test interpreter. And how do we, how can we test this? I think the easiest way is to just say, okay, we have a bunch of different orderings. And what we care about is the only thing that we can really test is the order in which those bookings come in. So we want to test if the order of the bookings is the same as in what we specified. So what we can do is we can just reify this booking into an actual value and then try to interpret our program into a value of booking and then see if the order is still the same. So let's have a look. So we create the sealed type booking, which can be either a drink, a room service, or a sandwich booking, right? So far, so good. And now we can create a test service, which is going to be a const of list of booking, right? So a const, in case you don't know, is just something that has two type parameters, but only stores something of the first. So a const of list booking and A, for example, is just a list of booking. This might sound a bit complex, but if you actually get around to using const a bit more often, it'll make sense, I think. So if you want to book a drink, we'll just return a list of a drink booking with the specific beverage. If you want to book some room service, we'll just return a const list of room service booking. And if you want to book sandwich with the dough and topping, we just return a const list of sandwich booking. And what this gives us, if you want this program, because const applicative instance is just the monoid instance for a list of booking. So what will happen is we will run the program using this interpreter and we'll just concatenate the lists together. So since we have a list of booking of drinks and a list of room service booking and then a list of sandwich booking and we had them in this order, we'll return a list of these things in that exact order. And now what we can do is we can run this using this test service and then get const, turns it from const back into the list of booking. So we get the list of bookings out of our book things program. And then what we say is, okay, we expect the bookings to be in this order. So it's also a list of booking. And then the only thing we need to do is assert that these are the same. So what are we actually testing here? Are we testing the external service? Obviously not, because we're only doing this inside of our own system. So the only thing we're actually doing is testing the internal wiring of our own system. And so I think that everyone has worked with a lot of abstract code that uses something like IO or maybe something that even uses future. You have a lot of wiring, like a large four comprehension that does call things here and things here. And it's easy to test the pure part of your program, but sometimes the wiring, where you would basically just call something from an external system, use some pure computation, call another thing, use another pure computations. The wiring is kind of where things can go wrong, or where things tend to go wrong because the pure part is easy to test. But the side effects are the messy thing. So this is what we're trying to test basically. So what we did was kind of easy because if you looked at it, all of the things in our algebra just returned unit. And usually we have to deal with much more complex types. So for example, we might want to call something to our email service, and that's going to give us back an ID that we'll handle that we can then later look up if that email actually returns something or if what it has actually sent. And we have to think about a lot more things than just fire something and forget. Like just write it to file, and I don't care if it actually wrote to that file or it throwed an error or whatever. And this becomes a lot harder. So what could we do? Two questions you should ask yourself. So if you mark an external service, does that external service just hold data? Does it just give you data? Or does it maybe just allow you to run some things without carrying with a return? Usually that is not the case. Usually external services also have some sort of state. Like the file system or a database, they both have state, right? So if you write something into a file system or into a database, now it has some change state. And so this is what's difficult. And the second question you might want to ask is that how can you or can you reasonably mimic the behavior of that external service using like a self-contained state machine? So for example, if you want to mimic a database, what we can do is say, okay, let's just use something like the state monad to basically say, okay, we hold a bunch of different states here and now we can actually model that if we want. So if the answer to both of these questions, actually if the answer to the second question is yes and the answer to the first question is state, then the next thing I will show you will help you a little bit. So let's look at a complex example. So this is what we want to test, right? So we have this function discount all and now we're in some different domain. We have like a web shop and we have some customers and discount type and we want to apply a specific discount type to a list of customers or not just to a list of customers to all customers. So what we say is, okay, let's just get all the customers from the database then log all of them. Then we want to get a discount for the specific type and the discount might change. So we actually have to call to an external service to get the accurate discount. Then we also want to log that discount. We want to update all of the customers. If they're eligible for this discount, we want to actually apply it to them and then at the end we want to traverse, we want to traverse the updated customers and log them basically. So this is something and now we have to try and test it. So let's try disentangling it first. So we can create different algebras. First we start with this logging algebra which is fairly simple. We know we want to be able to log a customer and we want to be able to log some discount. Okay, using the discount type and a discount. And now the customer service is a bit more complex because here we're doing a couple things. So get all customers is fairly simple. We just get the list of customers, get discount. It's also fairly simple for every discount type. We want to be able to get a specific discount. And now update discount of eligible is probably the most complex where we take a customer and a discount and we want to basically update this customer based on that discount and return a new customer that the external service gives us. So this is basically how we would rewrite the discount off function using tagless final. So we say for any F that's a monad, given a discount type and the ability to use this customer service and logging, we can create an F list of customer and now we just basically run the same steps that we had before. We get all the customers, we traverse to log, we get the discount, we log the discount, and et cetera, et cetera. So this is that. And now we actually want to have two interpreters, right? So the IO interpreter is going to be the same function that we had before. And now we also want to have an interpreter that allows us the ability to test this. And to be able to test this, we need to actually, this is where the mocking actually begins, right? So now we need to actually mock the behavior of that external service. So we need to write our own is eligible function that instead of calling to the external service is going to kind of do this locally, right? So if I give you a discount and a customer, if he is eligible for that specific discount, return true, else false. And now we also want to be able to update a customer by applying the specific discount to that customer. And now we actually want to mimic the state that this service has. And of course, this is just an approximation, right? We can never actually completely re-implement that external service, otherwise we wouldn't need that external service. We could just use this to, and we wouldn't need it. So what we do is we say, okay, this service has a state where it stores a list of customers and also stores some discounts where there is a mapping from a discount type to a specific discount. So this is going to be the service of the state that our mimics, our mocked service has. And now we can start writing this test interpreter and it's going to interpret into the state monad with the service state as our state. So if you know the state monad, this might make sense to you. If you don't, this might seem really, really foreign and I apologize, but I think if you can read up, there's tons of material online about the state monad and how it works. So if we get all customers, we just say, which is a state of service state and list customers, the only thing we need to do is call state.get, which gives us a current state that is of type service state and because service state has a list of customers and this discount type mapping, the only thing we need to do is map to the customers to get the list of customers back. For get discount, we just take that service state again using get and then we get the discounts, which is a map from discount type to discount and then we just apply that discount type to the map to get back our discount. So far, so good. Now the hard part is actually update discount if eligible because this actually has to modify state, right? Those two functions, they just get the current state and this last function will actually have to modify and didn't fit on the slide, so I created a new one and this is all of it. So this is something and it's a lot of things, but let's have a look to kind of grasp this in detail. So first thing we do is we use the state constructor which takes a state S, which is service state and what it will return is an updated state and also the value, which for our case is a customer. So what we'll want to return is an updated state and the customer and the first thing we'll see is that we use this if eligible function that takes the discount and the customer and if that is false, it will directly jump to this line here and that line will just return the things unchanged. So if this customer is not eligible, we won't change the state at all and we'll just leave it unchanged and return the same customer. If he is eligible, we'll update the customer using this update the customer function we defined. We'll actually filter out that customer because we just updated him from the list. So now we have this without customer is a list of customers without that specific customer and then we add them back in by using, by copying the service state and putting them back in using this plus double colon and at the end, we just return the updated customer right here. So I hope that kind of made sense but this is just kind of describing the complexity that you sometimes need. I think if you take your time and look at this maybe after you fully grasp the state monad it will totally make sense, I hope. And now what we can do is we can actually create a property based test where we say, okay, if we have a list of customers and we have an item, we can actually run tests that should work for all of these. So what we do is we define a discount that is just fixed, it just applies 20% on everything. We have an expected price that we just get by calling some code that I haven't included for time sake and then now what we actually care to do is we get this state by calling the discount all function which is the function that we saw at the beginning which is the tagless final that can be interpreted into both IO and the state thing and we use this test interpreter and a test logger that's just not gonna do anything. And of course we also use the discount type fix so that it actually applies the same discount that we have up top. And now for a state monad to actually run that state monad we need to give it an initial state that's what we defined here in the valid initial where we give it the customers that are gonna be generated from us and we also give it a map from the discount type fix to the actual discount that we defined up top and now to actually get that price we use this state run a function which just gives us the result. We give it this, we give it the, this should, I'm sorry, there's some, this should actually be initial right here. I think seem to have made a mistake. So we give it these initial value which then gives us an eval which we can unwrap using value and then we fold map using this item.price4 method. And at the end we get the price that we computed and now we can just assert if that is the same as we expected and now this should work if everything worked out fine and in our case it did. So this gives us the ability to test complex function calls, complex service calls that are effectful using our local service our local machine basically. So that was a lot to grasp. So let's just recap a bit. So what we can do with this technique is we can mock behaviors of external services by pulling them into our own world making them fully deterministic. Now we no longer have to say, oh, okay, this only works if I also have this other service if this also this other service is running and our HTTP is when we're using the same port and all of that, but this makes it like fully deterministic. It might not fully represent that external service but it gives us the ability to remain fully deterministic and that's I think the key. And of course this won't make sense for all of our services. For some service they're just too complex to just mock them with a couple of functions. And I think you should just like evaluate to see maybe this makes sense here, maybe it doesn't. In a lot of cases it won't be feasible but in some it can be a huge help. Just to basically be sure that your internal wiring, your huge for comprehension does exactly what you want it to do. And in the end that is what we want is to give us more confidence in the code that we're testing. Okay, so conclusions. That's the best thing to do is separate effectful code from pure code and make use of total functions that have well-defined inputs where well-defined means that they're restricted in the sense that in the sense that whenever we call it we only want it to work on the inputs that are actually going to be used by the function. And when testing side effects see if you can mock some of the behavior if you can that's fine as well. Okay, that's all for me. Thanks so much for listening. You can find me on Twitter at lukajokoboets and on GitHub. And if you have any questions I'll be around till tomorrow. Thank you very much.