 So, let's go. So, hi everyone, my name is Eric. I'm going to talk to you today about the EF monad. So, if you want to talk about the EF monad, you will learn about how this is a monad based on the version of a free monad with an open stack of effects and how it's implemented using continuations and all sorts of things. And that would be great and very enlightening. Like, oh, that thing is really cool. It's like the monad to rule all of our monads. That's nice. But then you come back to work and, okay, but how do I use this thing? Where do I use this thing in micro and project? Like, I want to build a microservice. Where does that thing, what does that fit? What kind of effects should I use and so on? So, today I want to give you an example of microservice that we built using the EF monad and how we create our software, how we design it and where the EF monad fits into all of this. So, I work for Zalando. That's a web store where we sell clothes online and it's actually a big store. We sell lots and lots and lots of items and also we have lots of engineering behind all of this because we don't do only the website but also all the logistics that goes behind. We have huge warehouses in Europe and we produce all the software for that as well. So, there's lots of smart algorithms for the logistics and all that we do as well. And in my department called Merchant Operations, what we do is that we onboard merchants and we let them sell their own articles on the Zalando platform. So, if you are like Zara, you can say, okay, I have some stuff to sell too and I can use Zalando as a platform to sell my own stuff. And in the context of this department, we had a small microservice to produce called the article service and the article service is a very simple microservice. So, if you're a vendor, you say, I have a pair of shoes I can sell. A pair of Adidas, I have a for a given price and I have that many items in my stock. You can use this service to register yourself and say, okay, I can now sell those shoes. And the article service does, it goes through other services. First of all, to check if you're a registered partner, if everything is correct in your contract then talks to the catalog to know if the shoes, they already exist or not, check the stocks and so on. So we started building this and right in the middle we learned that the new service for products that we were using which was brand new, we actually could not use it anymore for some reasons related to the legacy system and to some identifiers. And we had to get back to the old system which was, she's called ZEOS. It's the old legacy Java system which is a big monolith and so on. Right in the middle of the project. So what did we do? Well, we panicked, what do we do? I mean, we have to break everything that we did. It's horrible, right? You take some assumptions on how you build your software and then someone breaks everything. Well, actually we did not re-panic because we relied on a very well proven technology, something that's really nice. It's just called components. It's actually not so new. It has been described a long time ago. So if you read this paper on how to divide the system into components, the point that this paper tries to make is that it's not so much about dividing your system into different functionalities. It's also dividing your system into what changes and what doesn't. And the purpose is to isolate the rest of the system from what's going to change. Try to hide the implementation so that you can be isolated from change. That's probably the most important criterion to use, how you divide your system. So now you hear we are building microservice, we're using components, and you might think, okay, how do you wire them up? What kind of software do you use to do this? So what do you use for dependency injection? So maybe we're using something called the cake pattern. So I don't know how many Scala developers are in the room. Can you please raise your hand? Okay, a bunch of them. So maybe you've heard of the infamous cake pattern. So it was a trial in Scala to do dependency injection using some of the Scala type system features, like traits, mixings of traits, self-types, and so on. Unfortunately, it turned out to be not such a good idea for dependency injection. So maybe we turned out to a Java library, like juice. Juice is doing dependency injection. So maybe we could have used that or say, oh, let's forget about Scala and all this complexity. Let's go back to Spring, and because Spring is the thing, it's Spring Book got everything. So no, actually we didn't do any of this. And we tried to have one more radical approach, like let's try to go back to what it means to do constructor injection. And let's start to build something from this, because constructor injection is actually quite nice. You need something, you just inject it in your component using the constructor, and that's it. And what's missing? If you just use constructor injection, what could be missing from it? Let's take an example. So this is a piece of configuration where you can describe how many threads you need in your thread pool. And this is the configuration that's going to hold this piece of information, and maybe a lot more, like some URLs for your external services and so on. And you can have one version of this application configuration for production, one for staging, one for development. You can describe all of this in your code. And if a component needs that piece of configuration, you can just create a function. Say from config, that's going to take the full application config, it's going to carefully extract just the parts that this component needs for its own association, and that's going to create the component this. It's a simple function. Now I'm just going to slightly modify this function to do almost the same thing, but I'm going to use a reader. So the reader data type is also a function. It's just giving it a different name, it's going from A to B as well. So we can define in Scala an implicit reader that's going to create a reader object, taking a configuration, and returning an HTTP client. And now we have two components, one needing another. The reader definition becomes, I can create a reader for HTTP server provided that I already have a reader for my HTTP client. And then I know how to create an HTTP client, and then I know how to create my HTTP server. So I know how to create both components together and wire them together. And in Scala, if you know about type class implicit resolution, implicit resolution, you know that you can then wire up your full application. But there is still a bit of ball of play in the code that I showed you, because it's quite mechanical to write all this code. So we automated this to make it simpler using a library called Backdoor. And now you just have to put annotations on top of your classes. And you say, I want a reader for this. And it's going to create the same code. That's very easy. And once you have all those reader in place, it can be at the top level of applications. And now give me an HTTP server. You pass it to your configuration, and then you have one. So in a way that really solves the problem of the instantiation. It's a very simple and well-known functional pattern using a reader that we can really apply in the situation to instantiate the whole application. That's nice. So this is more or less what our application looks like. You have an HTTP server at the top. And there are different routes for the different services, like get a price, set a new price, get the stock, set a new stock. Those rights are HTTP routes. So they need to extract some parameters. They pass all of this to some business services, if you want. There's a bit of business logic in there. And then we talk to external services. They are all clients. So it's like a layered architecture like this. And this is one of the routes. So I won't spend too much time explaining because we're using a DSL for this. This is a nice, actually, in Scala, it's a nice DSL called row, which is a type DSL, where you can describe the routes. And everything is fully typed. And when you extract parameters, you know exactly the type of what you've extracted. So if you're familiar with Haskell, there is something equivalent called servant, where you can do the same kind of thing. So the job of this component is just to extract information from the route, from the incoming HTTP request, that's all it does. And then it sends the extracted parameters to a service, which is the price service. But that component doesn't want to know at all how the price service is working. That's not its matter, it doesn't care about that. So what we want for the price service is just an interface. We don't want to know about the implementation because then when we test the routes, we can be fully isolated from all the rest of the system. So we want to have something like this. We want to have a price service and then an implementation for that price service. But introducing an interface here allows us to really separate components. But how does that work with the reader? Because if you define a reader on an interface, I mean when you create a reader, you need to create a concrete instance. So in that case, you cannot use the reader annotation that I showed before. Because you need to specify, for a given interface, you need to specify what will be the at least one concrete implementation. So in that case, you can use this default reader annotation where you specify that by default, when you try to create a price service from the configuration, you will use this concrete implementation. And then at the bottom of the screen, you can see the concrete implementation, which is using other services. So this works quite well, but as you can see, this is already like a significant difference from other libraries doing benefits injection because there's always one default being specified. Yes. Why didn't we use AKHTP? Because we're using HTTP for rest as a web server for once. And also because the wrap in row, which is the layer on top of HTTP for rest, are fully typed. So internally it's using like HList, extracting all the parameters with their concrete type and gives you the types that you want. And it's even smarter than this because also the return type is fully specified. And then we can produce the entire swagger file describing our web service completely from the code, which is something that we have to internally, we have to produce swagger files describing our APIs. And instead of having going from swagger to code, we can directly go from code to swagger. And we never have to maintain this relationship, which is quite cool. That's the main reason. Okay, so now you understand that we're using readers, so that's cool. But you might wonder, hey, there's a big flaw in your system because it's always a reader from config, which is my application config, to a component. What happens if I want to put that component in the library? That component knows about a given context, that's my application config, but what if you want to reuse it in another context with another application? That's a problem. It's not modular. So actually it is because the reader annotation is slightly smarter than this. And what it does, it says, it creates a reader that says, I can build an HTTP client, provided that you know how to extract a thread pool from an A. And I don't really care what the A is. So in our context, it's the application config. In another context, it could be something else. So this implicit here is just a requirement that says provided that you know how to extract it from A, I know that then I can build from that A and it should be client. So this is a solved problem. This is not a problem. And that's cool because we also extracted some of the components in two libraries and now we're able to reuse them. And the last piece is now that we want to be able to extract a thread pool from an A, that means that we need also to provide implicit going from our concrete configuration to a thread pool. And there's another annotation for this. So basically there's a set of like four annotations that generate all the boilerplate you need to instantiate all your components. It also, so mostly what we've seen is a very system for describing components because the only thing we require is case classes, which is in Scala, like simple data structures and interfaces. It's not really fancy. It's not, there are no type parameters and so on. But you can actually also use a type parameter as a return type for your methods and the reader annotation will also generate the correct reader code in that case. So it's this style of writing components where they are parameterized by a type constructor is also possible. So it's really readers all the way down. Instantiation equals readers. That's nice. So what kind of problems, what have we solved so far? How is your life easier? So unit testing is definitely easy, right? There's the easiest Rubik's Cube in the world. This one is easy because just take your components in like whatever you want using the constructor and there you go. You can test your components in isolation. So that's one is easy. You might think another thing might be actually a bit harder and this is usually the thing that gets really hard when people just want to do constructor injection is how do you do integration testing? Integration testing where you build your whole application and you just want to mock some of the lower level components to make them return pre-canned responses, for example. How do you do this? And this is annoying with just constructor injection because you need to manually rebuild the full tree. So the answer we have for this because this is a tree is, well, there's a theory of tree called tree rewriting and there is even in Scala a nice library doing this called Kiyama. So Kiyama takes on the experience of tree rewriting which is as a concept started many years ago and especially another project called Stratigo. So Stratigo has explored what it means to rewrite trees and Kiyama has imported those concepts into the Scala world. So in Stratigo and Kiyama you can define strategies that are going to be applied in your tree and transform the tree. So we can have a strategy for replacing nodes, for example. So what does this strategy, what does it say? It says, okay, I'm traversing the tree and if I find a node that has the type I want to replace I just switch it with another value and that's it. Otherwise I don't do anything. And then you have combinators which can use strategies like this one everywhere top down which is going to use the strategy and try to apply it everywhere top down until it reaches the bottom of the tree. And with this and a bit of syntax it's actually very, very easy to write tests because you can say build my HTTP server from any kind of configuration and we've seen that it's possible with reader and now replace, any time replace just this component at the bottom of the tree with the mock one and it's just one line of code. And for us it was very important that writing tests should be as easy as possible because you want people to write tests. You want people to think about tests about, I have to wire up all of this manually and this is going to be very tedious because then people don't write tests and that's not what you want. There are still some problems to solve though because actually what we are building when we are instantiating the application is a tree. It's really a tree and sometimes you don't want a tree. You want a graph because some of the components you don't want them to be duplicated. Some of them are doing database access. Some of them are holding connection pools. So in that case what you want is transform this tree into that graph where some nodes are being shared by others and the reader pattern cannot give you this. It cannot give you this. So what you need to do again when we're a tree writing helps is that you can define another strategy for doing this. And this time you're going to work down the tree. The first time you find a node that has the type that you want, you put it on the side and every time you find a similar node of the similar type, you replace it by the instance you first met and this way you're going to rebuild all the connections just to one node and you're going to make your singletons like this. So this is why in the first slide I showed about the main application. We were first creating the HTTP server using the reader for the configuration and then making singletons because most of the time you want your components to be singletons. Actually in reality it's slightly more complex than this and in our case it was slightly more complex because some components have the same type but they need slightly different configuration. So we have two operations, one called modify with which is going to practically modify the tree and put some configuration in some places and the other one is called singletons by that's going to make singletons based on the fact that two components have the same configuration or not. Let's make it more visual. So this is our tree that we got from the reader and as you can see master data is duplicated you know in two of the branches, okay, it's fine. So the first thing to do is we modify the tree and we put a different client configuration for master data and the catalog because they are both using an HTTP client but you want their HTTP client to be configured differently because there are different services. So this is what modify with is going to do. The next step is going to make singletons but this time based on the fact that two things should be made a singleton only if they have the same configuration. So the second call is going to make the singletons but this time not unifying everything all at once based on the type but it's going to unify based on the type and all the value that is in a current node. So eventually we get exactly a tree with the shape that we want with all the parameters that we want. And we can go further so we can also visualize that tree. So this is one of our applications because we have a tree, we can output a dot graph and we can verify that this is exactly what we want. But this is actually quite nice because you also have some kind of architecture diagram produced for you automatically. One very important point though is that because we are doing tree rewriting every time you instantiate a component it's usually not the first time you will do it because every time we rewrite the tree you re-instantiates a new component. So all those components in their constructor need to do absolutely nothing. Do not start any service, don't do anything. So if you need something like an execution context you need to wrap it into an eval monad or make it lazy in some way because you don't want this thing to start every time you create a component otherwise you will be starting it many times. If you want to start stuff you can implement the start interface and implement the start method. And once you build all your application once you've made your singletons you've configured everything you can start your application. And starting the application is again using tree rewriting. Just going down the tree if a node has the start interface we call the start method we collect the results because maybe it can fail. And eventually we grab all the results and we display it to the user saying okay it went okay or it didn't go okay. And what we are doing here also for traversing the tree is slightly different than what we did before is we do an everywhere bottom up because generally you want to start the services bottom up from your dependencies first and then from your higher level services next. This is a reasonable default. If you have a more elaborate starting strategy you can implement your own combinator to do a different traversal of your tree. Okay, so this is how we do dependency injection. We use the reader pattern that any functional programmer should know about. We use tree rewriting to put the exact configuration we want where we want it. Stay lazy, that's very important and then we can start the application. That's quite cool. But wasn't it to talk about F in the first place? No one is shouting, I want to see F. It's coming because now we have nice components. We have things that can be developed independently put in libraries and so on but those components have interfaces. An interface is generally a bunch of methods where you have the name and some input parameters and a return type. And what we want to do here is to be very precise on what the component does and we do this using the F monad. So you can think about the F monad as being a monad. With this additional type parameter here R which is going to track the kind of effects that you're doing. Maybe it's doing some IO, maybe it's doing some concurrency, maybe it's doing some logging, maybe it's doing some state keeping, all sorts of stuff. So unfortunately I don't have the time to go deeper into the subject. I will ask you to have a look at this presentation. I did a flat map of flow. If you want to have more details about the free monad and the F monad in particular I just want to give you some kind of flavor of how you think about the effects. So this is a computation using the either monad. So the either monad is for when you have the possibility of having errors. And in this case an error is just a string. So you do two computations that can both fail and then you can print the results if everything is okay. It's a right value. If it failed you get a left value and then you can say oh there was a problem. Now if you think about the same thing as an effect you will consider that the left case is a request that you make to someone else which is going to be an interpreter and you say oh I got a problem. I don't know what to do with it. And you decide. And if you have a right value you say oh I was able to get value. That's a right value. That's an A inside my right value that you can use. And generally the interpreter is going to say oh you gave me a left request. I don't know what to do either because there's no value that I can continue with. But if you give me a right I know I can extract what's inside the right and then apply continuation to it. Because there might be other computations like for example in the for comprehension. There were other things to do. So this is what the interpreter is doing. And if you can rewrite the same codes that we've seen before with an either effect and it will be almost the same with a bit more type annotations. And the run either here is going to be the interpreter that's going to interpret those effects that you put in your for comprehension. So if you look at the F library in Scala you will see lots of effects out of the box. From reader, writer, state, I mean the usual suspects in functional programming. But you can implement your role. So typically our interfaces they look like this because we generally use concurrency. So we have task effect. We do some logging. So we have a log effect. And we do some flow ID which I'm going to talk about. And then what we return is an F value with this R type parameter which is describing the stack of effects that we're using. And of course. And more precisely this whole definition says I got a R which is my stack of effects. Actually I don't know exactly what R is. I just know that at least it must contain a task effect, a log effect, and a 5D effect. So it's still open about what R should be. And eventually you say oh now I want to really invoke that function and I want to invoke it and get a real result out of this. So then you say this is my stack. So my stack is precisely those three effects. Please invoke the method with that precise stack and then call all the interpreters. And because you've given a special stack and because everything is nicely tracked in the type system, you can only call those interpreters and no other interpreters and you will not get the value out of the F monad unless you've called all the interpreters one after the other. So let's go back to the article service and talk about this flow ID thing. So what is this flow ID that we implemented? So a flow ID is something that a user of the service is giving us to track a given request. That's a very important thing to do in macro services where you send a request but generally this triggers other requests to other services and when you read the logs, it's nice to have one identifier able to tell you exactly what's happening on the different services. So this flow ID has to be transmitted to every method in the call chain, right? It's kind of annoying to have to do this manually, name a parameter, pass it everywhere. So typically this is a good case for a reader. So because there is a reader effect, we implemented a reader for flow ID and a bunch of type aliases in Scala and this is what we do. We can ask for when we are at the bottom of the application, when we really need a flow ID to be materialized to pass to the downstream systems, we just ask for one and it has been implicitly passed across all the method invocations and now we can send it to an external system and if we want to run this method, we need to run the reader, we need to interpret it with the run reader interpreter and pass it the flow ID coming from our client. Yes, yes, yes, yeah, it needs to be passed, yeah. So I mean syntactically you don't gain a lot, you just gain the fact that you don't have to name a parameter and also you don't have to pass it to every call. So let's see another example, logging. So how do we do logging? So we have like an entity for logging. With all the different cases that we want, like info, word, debug, error, and so on. And we can make this an effect and maybe I won't spend too much time on this but the send method is a method provided by the F library, taking any kind of ADT, like a T of V and provided that T is part of your stack of effects, it returns you an F value containing that thing. This is how we send an effect of a given type into our large stack of effects. And then we need to write an interpreter for this. And in that case what we do is that what we really have to interpret those effects is like a concrete logger, so like a type save logger from the type save libraries and which is going to do a real call. In that case, because we are doing functional programming, we really want to evaluate things, especially IO things at the latest moment. So what this code is doing is doing a translation from an effect that's pretty abstract because just a bunch of requests, hey, I have an info logging, that's what I want to do. Hey, I have a debug logging message, that's what I want to do. And it translate this to a more concrete action which is now use a given logger and write to the file system with this. So we do translation from one effect to another in the stack. But there's more importance and a nice thing that we can do now is that not only we can write one interpreter that's going to take our abstract effect and do something concrete, but we can also write other interpreter and some very useful ones. So we can say for example, hey, discard the logs. I don't really want to write any logs. So that's cool thing when you do some testing because sometimes you see people writing tests and then when they run the test, there's logs all over the place and they cannot see anything anymore. But with discard logs, you can say, I don't want to see the logs. I'm not interested in that, just run my test, please. And at other times, we are really interested in the logs because you want to test them. You want to test that you put the right information in your logs. So in that case, what you just want to do is collect them and this collect log interpreter is going to just return you your log messages and you don't care about the value. So you can see what the power of having effects and different interpretation is. It's very flexible. The next great thing about the F-monad, especially if you read the original paper written about the F-monad, is that the F-monad originally is just a monad. So it's very sequential. So you do A, then do B, then do C and track all the effects between all those actions. Here it is. It's a free-air monad. Yeah, so if you read the papers, yeah, Google for free-air monad, absolutely. But in the F library, we implemented also an applicative case where we can have different effects being really executed concurrently. And this is particularly important for us as we are talking to other services. We want things to be concurrent because we don't want to waste time waiting for too many services in a row. So if you want to get the price and the brand of something, we can just do an applicative union of those two calls and then map the results together. We can also traverse a list of articles and make sure all those calls happen concurrently. That's quite useful. So we implemented all these applicative concurrency. It's actually backed up by lots of concrete data types. So there's an effect for Scala Future. There's an effect for Twitter Future. There's one for Scala Zed Task, one for Monix, one for FS2 in Scala. So if you know Scala, there are lots and lots of options for doing concurrency and futures and tasks. And there's a new one now, Scala Zed IO is coming. So all of this is currently supported by the library, except no, not Scala Zed IO, it's just too recent. So you can eventually end up, I mean, you can use the concurrency library of your choice and it's integrated with F. So we've seen like different effects that we are concretely using in our services. One of them is floaty, it's like a reader effect. Logged is more like a writer effect. We have eval, which is for delayed actions and tasks for concurrency. But there's no reader for config. And maybe you've seen some other talks about how you can do dependency injection using the reader monad and at the level of functions, not at the level of components. And this is not what we do. We do dependency injection at the level of components, never at the level of individual functions on all components. And in my experience, that's also a great separation of terms that really makes instantiating the application really separate from using it. Okay, we had some learnings while we try to use the F monad on the real project. So maybe you can learn about that. The first thing is that in some places we started using describing calls like that. We're saying, okay, if you want to get a price, I will return you an F value with this exact stack containing tantas, logged and reader for flow ID. And this makes your life really difficult if you do this because if you have another call with a different concrete stack, you will have to reconcile the two stacks together. And that's, it's not impossible to do, but it's actually quite hard. So what you want to do inside is to leave, to use different style of declaration and to leave the things a lot more open. So this declaration just says, I'm using a stack of effects R and I just know that this stack contains at least task, logged and flow ID. But I don't know exactly what it is now, that I don't care. And then it will be a lot easier to integrate with other calls having other effects. So that's one interesting thing. There's the same thing in Haskell. If you do Haskell, you will have all sorts of monad transformers and they are quite rigid in that sense because if you have different stacks of monad transformers, they might not play well together. If you use the MTL library on the other end, you just have type classes constraints. So that's the exact same idea. Error management can become also quite a challenge if you have different layers because now you have three different possibilities for handling and describing modeling errors. So you can say, either return an easier value, that's one way of doing it, or you could return a data type that's also encapsulating the possibility of an error. That's something that we did. Or you can use an effect and just return an A, but in your stack you have the possibility of declaring errors. That's the third way. So which one should you use? Well, actually we used all of them. And this was a bit messy at first. So after a while, we decided to have some kind of conventions. Like the lower level was always returning explicit error types like either and so on. In the middle, we had more usually using either effect or sometimes more complex error types. You really have to decide how you want to model errors because now you have a lot more possibilities. One thing that we found really cool though that you might not understand right away by reading the slide is that locally, you can add effects just to your stack just for a short while. So you can say, I'm going to do a bunch of calls and I'm going to pretend that just for those calls for a moment, I have this new effect in my stack. I have variable either. And that's quite cool because this allows you to just call the happy pass and forget about errors for a while. And then only at the end, you reinterpret either to get back at either value. So this is quite nice that we can do this kind of stack manipulation by just enriching the stack for a while and then decreasing the stack when you're done. That's really helpful. Another mistake that we made is that if you read tutorials on the free monad, most of the time free monads are modeling like command to another system like as a free DSL for everything. And that didn't work too well. For example, we decided to, let's try to see if get price could be modeled as a free monad. Like it's an action, it's a command after all, get price. So where do we go with this? So if you want to have a component where get price is described as an effect, well, you need a bunch of baller plates to describe your NADT for getting some price. And then you need to describe the method to get price that's going to return the price request effect. And then you need the components which is holding your interpreter to then interpret that program containing price effects. And eventually what you end up doing is, okay, use the components to build a program. The components gives you back a program containing this price request effect. And generally what you want is then, well, interpret that thing into async, for example, and maybe some other effects and get back a result into async. So this kind of dense is always the same and it's not very useful. And it also makes the interface of our components pretty complex in terms of types. And not only that, but all those effects, there's DSL effects like get price, get stock, and so on. They tend to bubble up to the highest level of our application. And then we have a huge stack of effects because it's containing like low level things like concurrency, logging, and so on, but also DSL stuff. So this is what we decided not to do this and have our components return really low level effects like things that are like technical concerns, async, log, and so on. Yes, no, this is still on one. Anyways, yeah. Actually the implementation is more complex because it's not a list. The stack of effects is encoded as a tree for performance reasons. So this is the kind of interface we end up with just using low level type of effects like technical ones in our interface and not DSL ones. And maybe to finish up with, we can discuss about another component that we use which is about time. So if you want to, it's very useful to model time as a component, especially if you're doing functional programming. Because if you want to know what time is now, this is a side effect, right? This is an action, if you do it twice, we'll get two different results. So this is important that this thing is well encapsulated. So should it be an effect or should it be a component? So let's try to see what would be the difference between the two. So for example, we use this time effect or this time to implement a right clicking. So if you want to implement right clicking, you need to know what time it is. So you have a component for right limiting and if you implement a component for time, it will ask that component, hey, what time is it now? And that component will reply with the time it is now. And if you want to test, you can use Grafter to just switch that component for something that will always give you the same time, always. Or which is just going to give you a list of times and which will cycle through this every time you make a new call, for example. So you can really control the passing of time. So this is definitely a possibility. Another possibility would be to use the time effect. So this time when you get the status, you get results inside the time effect and your interpreter for the time effect could either do the real thing, push the real time, or do some for some testing. You could have an interpreter that just returns always the same time. And the major difference here that we see is that most components generally are really black bits. So when you do something with a component, you don't want to know about the implementation. Once you add effects to the interface of that component, like time in that case, you open to the client of the component the possibility to do things slightly differently. So for example, the client can completely ignore that effect, like the logging. We can discard all the logs. Or it can just use a different notion of time. Or it can take asynchronous calls and make them completely synchronous. That's another possibility. So I think in terms of design, this opens like a new possibility when you do component-based design. So your components become not completely black box, but they kind of open up to the client a bit of control about the effects that they are generating. And this is quite cool as a design possibility. So in a nutshell, what did we do today? I want to re-enphasize, and I think that components are really important. Because we need separation of concerns, it's nice to have interfaces. It's how when we have systems, especially business systems that are evolving pretty fast, that's how we can make sure we can isolate part of the systems and make sure we don't infect. If there's a change, the rest of the system will not have to change too much. I think the constructor injection is quite good. If you add a bit of syntactic sugar on top of it, and that's what we've tried to do with Rafter and through the writing, and we get something where we can be really efficient. So if I have a component and I need to add a new dependency, I generally just have to add a new dependency. I don't have anything else to do. I don't have any reader to modify anything else. I just need to add my dependency. So it's really nice because we can evolve the application. We feel very productive with this. And eventually what we need to do on our services is to make sure we have a precise definition of what they are doing. And the F-Monad is a great way to do this because we can be pretty specific about what they are doing. And we also, we can give a bit of control on the clients about all the low-level technical effects that we're using if they want to do something different. So please use the libraries. Give me some feedback. Come and work with me at Xanando in Berlin. If you're interested, we have lots of things to do. And that's it for today. So in a way what I was saying about Monad Transformers is that they are very fixed. So if you have a reader T of writer T of option T of something of IO, it's a data structure that you, it's hard to make it interact with let's say an option T of reader T of IO. Which would be like in a different position for example. Or with another stack that has either T in the mix. The F-Monad uses what's called an open union of effects. So just says that you have at least those effects but you can have more. And it makes integration between two different calls having different effects a lot easier. So the order does not matter in the definition of what you're doing. It matters a lot in the interpretation. So the order in which you do, you run the interpretation. So the classical example, if you run either and then state, you will get a different result as if you run state and then either. But this is also tracking the types because the type that you get back is different. The first one will be a pair of the states and then either value. Second one would be a left value containing an error or a right value of the state. So in the constraint, it's just a set. No, if your interpreter's commute, you can switch them. If they don't, you can't. No, it's also, the implicit are smart enough to be more or less resilient to changes in order. I cannot completely guarantee that because it's quite complex, but yes. So I think you need to put a bit less. Most of the time because it's just using implicit. So the notation we saw with the type parameter and a bunch of column underscore or something is just some syntactic sugar for using implicit. And implicit, they tend to flow pretty well. You couldn't have some problems sometimes when you call the interpreters themselves, but otherwise it's okay. Yeah, so it's not so good, but that's, I mean. So one thing that the freer monad does over a classical freer monad is that it solves the issue of quadratic bands, right? If you associate to the left different flat map, flat map, flat map in a normal freer monad, you rebuild the structure every time you do it. That's not very efficient. The F data structure solves this and that's fine. But that being said, you are still in your program building a data structure and then interpreting it. And that has some cost in terms of allocation. No, because what we're doing is really calling services and IO is like dominating everything. So this is not a problem. That being said, there's a very interesting project in Scala called Effects with a K, which is using the limited continuations to pass interpreters or handlers directly to where they should be used. And this is a lot more performance, but it cannot do things that the F monad can do at the moment, which is handling resources and properly closing resources and doing applicative, which is quite important for us as well. Yes? Yeah, one question I had is, have you found debugging to be a challenge? Because I've started using the FS2 library recently and when you do something like run log there, so all the effects get run, but when something fails, it's very difficult to sort of track where exactly, which effect exactly failed, or which line in your code cost that failure. So that is something I've found a bit of a challenge. With FS2? With FS2. Is that a challenge or, so you run all these effects at the end, right? There's a failure or something, a stat base gets printed out and from that, it's very difficult to sort of figure out where that effect is. I think the main issue is due to concurrency. It's the fact that computations are being sent to the threat pool and then you don't have the full context of what created that computation. So yes, this is definitely a problem. But I don't think it's a problem that's really related to F. If you don't use concurrency, it's quite okay. The only issue in a way when you read the stack traces is that you have lots of garbage because you have all the F machinery that you have to read and then all the, I don't know, the cat's machinery for functional programming and then you get to your line of code. So that's maybe the only issue. Yes. Be able to run through all. Sorry. I was here, but someone else? Oh yeah, yeah, yeah, yeah, yeah. Okay, please, please. But once I realize that it's exactly what I did. I run through all the indices. Where's the message? I ought to go there. Okay, start again. Question please. Try success failure. So, oh, no, so there's an either effects in F and there's also like a more elaborate error effect where you can track failures, errors, or values. So that's also, so that's also what we get with the different error effects that we have in here as well. But we also have there's a safe effect which is quite interesting because it allows you to use a bracket operator. So a bracket operator is when you want to like use a resource and if anything goes wrong, you want to make sure that it will be cleaned up afterwards. And this is actually quite hard to implement properly. It's exactly like resiliency. Yeah, very similar. All right, so we'll have to set up the next week. Thank you.