 Hello in the warm welcome to all of you. We have our group with us to talk about interpret your monads, concrete monads versus monads of classes. I'm believing this is a wonderful session. So with no further ado, over to you, Alfred. Let me first go ahead and try to share my screen. Welcome to the talk. And I have to say thanks to the organizers for hosting this event. And it seems to be going great. I'm very, very happy to see all the activity going around. So that's really, really inspiring. I want to talk about an aspect of the Haskell language and how we deal with effects in Haskell. Before I do that, I want to say a few words about Haskell, which is the company I'm working for. At Haskell, we're building a data access solution. And from a technical or abstract point of view, you could perhaps see this as a GraphQL to SQL compiler. And back end is written in Haskell. And so that's why I'm talking about Haskell today. We are hiring. Please check out the link if you're interested. You can also talk to us at the online conference booth, which is happening, I think, even simultaneously with this event. Yes. And this talk touches on some things that have been had that we've done at Haskell, but they should apply to a more general Haskell programming. So first, for some motivation, here is a piece of code of our test suite. It's an exhaustive test. It tests over all Unicode characters. Hey, why do I see my mouse? Does my mouse visible? Yes, it's visible. Perfect. So you see over here that we do a loop over all possible characters and test some property for each individual character. And this is happening using the Hedgehog property testing library. And that means that this is a bit of an abuse of the Hedgehog library because it's built for property testing and not for unit testing, which is what this is. And indeed, in this code, we see a memory leak popping up. The memory usage grows to 2 gigabytes, even though this is only a very simple test. And that's due to the nature of the Hedgehog shrinking mechanism. In this particular instance, we're not interested in Hedgehog's advanced features of this shrinking mechanism or anything. It's just a simple exhaustive test that we quickly wanted to include in our test suite. And so my question is, can we quickly improve the performance here without swapping out the testing framework, without having to rewrite a bunch of code, et cetera? And the answer is yes, somewhat. So here's the change that I make. I add one additional method in the calls. And it's an existing method from the Hedgehog library that we insert, and it doesn't change the behavior. It doesn't change the correctness of this test. It's exactly the same test, but now it only uses less than half of the memory that it used before. So this is a quick and dirty way to improve performance in this particular case. And let's be honest, you can't always afford to write great code. So in this talk, I want to talk about what just happened, why this small method changed memory behavior so radically, what general principle underlies that, and how we can understand it in a most general setting. So this talk consists of four sections. And as announced, there's time for questions after each. So do please, in the meantime, use the Q&A feature to collect questions. So the story is that several times over the last years, I encountered a situation in which it made sense, and that's going to be the trick underlying this, which made sense to mix concrete monads and abstract MTL style code. And there's a seemingly general principle behind this, which I have a feeling is also under the radar understood by a lot of Haskell programmers. But it's not often discussed explicitly. And so I want to talk about it explicitly today. So we're going to discuss about what is, so I'm terming this effect interpretation. And we're going to discuss what this effect interpretation is, why it does what it does, why it can have such a big impact on runtime behavior, some other applications, some tricks that you can do with this general principle. And finally, we'll start working towards a mathematical understanding of what effect interpretation is, and in particular, when we could consider it to be lawful. And eventually, I'll talk in increasing detail about categories of monads, monad transformers, and the history behind them, eventually working towards the M Morph library. And I guess at some point, we'll run out of time while nerding out on category theory. So first of all, what is effect interpretation? So when you write Haskell code with effects, we usually use monads to be able to express these effects. And there are, roughly speaking, two main ways in which you can specify the side effects of a particular piece of code. One is an abstract way, which is usually associated with the MTL library, where you specify abstractly that you require a monad with certain side effects. And because of the abstract nature of this approach, you get more guarantees about the code, because it has less expressive power, whereas another approach is to use concrete monads along the way. And this gives you more control, because you always know exactly which monad you're in. And it gives you fewer guarantees because of this added expressivity. So one thing to realize is that even if you're always using such abstract specifications of your side effects, at the end of the day, you always still need to instantiate them. Like when you finally get to compiling and running the code, you still need to instantiate the effects to a concrete monad. And so the usual approach for doing this is that you have one global monad that somehow encapsulates all of the side effects of your programming. This can be called app. So here I introduce the app monad, and it specifies that we have two side effects, namely state and errors. And when you choose such a monad, then that means that all of these code written in abstract effects gets instantiated to the same global application monad. So this means that they're immediately compatible and that they can be run. However, it's perhaps not an ideal choice of instantiation. And I'll make clear later what exactly the problem is with this instantiation. But it could be more accurate, in some cases, to only instantiate what you need. So for instance, for F1, F1 only has an error side effect. And so you could say, well, why don't we just instantiate it to the exception monad only? And F2 only specifies that it requires a state side effect. So why don't we instantiate it to the state monad? And F3, well, it seems to require everything that our application monad offers. So let's just instantiate it to the app monad. The reason that we don't do this normally is that now these three methods work in different monads. And hence, in the first instance, they're not easily compatible with each other. They cannot call each other easily. So that's the reason that we don't normally do this. So that's one translation between these two boxes, namely that we always have to take abstract code to concrete side effects through instantiation. And what isn't being discussed explicitly yet and what I want to talk about is that there is also an error in the opposite direction, which I'm going to call interpretation, where you take code written in a concrete monad, such as the either exception monad, and generalize it to an abstract monad. So how does that work? How can we do this interpretation? And another question you might have here is what happens if you go down and up again or up and down again? What happens if you do round trips among these errors? So I'll show you how to do this interpretation. Here I take a concrete reader effect. Like I take an output value in the reader monad and I interpret it into an abstract monad reader m. How can you do this? Well, this reader effect is just a function, right? So we need to evaluate it on some input value r. Where do we get the r from? Well, from our abstract monad. So we just ask for it and evaluate the incoming function on that value. And now we've generalized the concrete side effect to an abstract side effect. Similarly, we can do something for writer. So if you have code written in a concrete writer monad, then it can be generalized or interpreted to an abstract writer simply by telling the value that we've been passed. So we actually write that value. And then we return the value a as we're supposed to. One thing you might wonder is, well, why do we need to do this? How does the compiler, in which sense are we forced to do this writing operation at all? Can't we just only return the value? This has the same type, right? Like what's the problem with only returning the value here? Well, it's unlawful. It doesn't satisfy certain correctness laws that we have to specify that I'll make precise later on. And I'll do that after discussing the mathematical foundations of this idea. In fact, this interpretation of writers into abstract writer monads is already part of the monad writer type class. Like it's already there as the writer method. And this confirms my suspicion that that effect interpretation is a widespread technique, which is just not discussed directly. So what other interpretations of concrete monads exist? So here's a couple that I found. So what we already saw, the Eider effect can be interpreted into an abstract monad error. Readers and writers can be interpreted as we discussed in the previous slide. State can be interpreted. We just fetched state from the abstract monad. There are some arguments missing here. You fetched state from the abstract monad, evaluate this function, and then put the new state. So there's nothing surprising going on. We can lift IOL operations, which is a kind of interpretation. And the identity side effect can be interpreted in an arbitrary monad. There are also some notions of effect interpretation that are never possible, such as the continuation monad. And there are some cases where it's not clear. So here are some things to think about. What is the required information to interpret something into an abstract monad, which is both a reader and a writer? Or can we also interpret concrete side effects into something weaker than monads, such as arrows? How can we interpret monad transformers? So the summary of the section so far, and this will also be the time for you to ask questions, is that there is, so code is often written in terms of abstract side effects using MTL. And if you do that, then the code still needs to get instantiated to concrete monads. The choice of the concrete monads you choose to instantiate to might matter a lot. And I'll discuss next why it matters a lot, which might raise the question, which monads are good or problematic to instantiate to. And then conversely, there is a notion of interpretation from concrete effects to abstract effects. And some of those interpretation methods are easy to write, but some don't exist, and some are unlawful, and some it's not clear. So this raises the question of when it's allowed to use interpretation, and also when it is actually useful to do this. And finally, there's the question which I raised before of what happens when we combine instantiation and interpretation. Are there any questions at this point? No, you're good. Oh, yeah. Actually, someone has raised their hand. Probably right now, we cannot take such questions. We won't be able to take tight questions. Let's see if we have time for audience to ask questions. And I'll move them. That's fine. I will continue. If questions about the first section come up, then please do interrupt me. Yeah, sure. So what is this effect interpretation? How does it work? But what's happening here? So what I want to explain is why the particular instantiation to the global app, monad might, in some cases, be undesirable. And so why the arrow here to the left is problematic. So let's look at how the monad instance for, OK. So I should say here that this global app monad is defined in terms of monad transformers. So let's understand better what's the monad instance for monad transformers looks like. So here's the one for the state t. So a monad is why I didn't show I cut out a few things right here. So I'm not showing the return operation. What's important here is the bind operation. That's the problematic part. So the bind operation, which is being defined here for the state monad, it's defined. Well, what do we do? We somehow run the state t and then run the state t. There's a lot of somehow some kind of effect juggling that's going on. And maybe in this syntax, it's not really clear what's happening. So I'll rewrite this by changing the do syntax to spelling out the binds explicitly. And then we get this. So this says that the bind for the state monad transformer is actually defined in terms of the bind for the underlying monad m. And that's where our inefficiency comes in. So put differently, if you're writing in terms of if your code is instantiated for the app monad and it uses the bind operation, it's going to call the underlying bind operation from the underlying monad until it reaches the bottom here. And that also means that if your code uses a very thick effect stack in your app monad, but you have a particular method that only uses one effect, it's still going to call the bind operations until it reaches the bottom. So here's kind of the visualization of that. So that's supposed that you have an effect stack with four. So you have the underlying monad, you have IO, and then three transformers. You have the state effect, accumulation monad, and an exception monad. So for each of those, each of the invocations are bind on the entire app monad, the bind operation defined for the exception monad is going to call the bind operation for the accumulation monad. That's going to call the bind operation of the state monad, and that's going to call the bind operation of the IO monad. So in realistic effect stacks, the bind operation follows the chain of effects on each invocation of the bind operation. And what I've tried to visualize here is that it might even invoke the next layer of bind operations several times, such as in the case of the accumulation monad, which is the underlying bind operation twice, like the bind operation of the underlying monad, it uses it twice. Whereas if one particular method in your program only uses states, well, the only bind operation that you would want it to use is the one for states and not any of the other ones. And so by first instantiating that monad to a particular precise, sorry, by first instantiating such abstract methods to a particular simple monad, such as states, not state T, but state, we have simplified the chosen implementation for the bind operation. And we only get a single link chain of bind operations. So this cuts out all of the links of the chain that we're not interested in. And then by then interpreting the precisely instantiated code abstractly again, we regain the compatibility and the abstraction of abstract effects. But now, after this interpretation, the effects of the precisely instantiated code would have been isolated from the rest. And so we don't suffer from inefficiencies elsewhere in the chain. So to clarify, this effect of maybe I go back to it, this effect that we saw over here, it's the reduction in memory usage is not a matter of specialization. It's a matter of the different choice of bind operation, the simpler bind operation, which is a very reduced memory usage here. So in the opening example, we avoided some memory leakage without any real effort to speak of. So we might wonder, well, what's kind of the general recipe here? What's the cookbook scenario in which we should do this kind of a trick? The ideal scenario is that you have calling code or maybe application code, which has a lot of effects. And these effects are instantiated by an effect stack, so by monitor transformers. And there are some call Lee code, some code that's being called that only uses one effect, but it uses the bind operation a lot. So you could use this kind of a technique to avoid or reduce some memory leakage without any effort. You could also use this to optimize code that is quite simply being very heavily hammered and where every percentage of performance counts. So in summary, the bind operation for moment transformers suffers from a kind of weakest link effects, where even if you're running a particular piece of code that doesn't use the majority of the chain, it still suffers from inefficiencies elsewhere in the chain that it's not interested in. And by using something like effect interpretation, you can cut out the rest of the chain. Questions so far? Yeah, there is one by Philip. It says, I'm not sure I understood how the functions in the first section are interpreters of concrete monads into monad type classes. Many of the functions did not have the concrete monad to the left of an arrow. This was on first section. So I'm not entirely sure I got the question. So as I speak, it would be great if it can be clarified. So here is one method, this lift test, which takes code in a concrete test monad. And it interprets it into an abstract monad M. And of course, there is a condition on M, which is that it is an instance of monad test. And similarly, where did I have it? Here are five more interpretation functions. And they go from concrete monads over here, either a reader, a writer, a state, IO, and identity to abstract monads on the right-hand side of the arrow. So they interpret from concrete to abstract. Does that help? Is there any clarification on the question? And once again, I'm missing some type parameters here. I'm sorry for that. It should be monad state, S, M, and monad IO, M. There's a comment from Philip again, where it says it doesn't say reader, RA, it says R gives A. Ah, right. So I'm sorry about that. Yes, so this is the underlying type of the reader monad. That's exactly the same as reader RA. So a reader RA is literally a function from R to A. And similarly, writer WA is exactly a tuple A, W. Well, that's one of the possible implementations. But it's the canonical implementation of the writer monad. And the underlying type of the state monad is a function just like this. So I've simply spelled out the types. I've spelled out the monads explicitly. Does that help? Yes. Great. Oh, and by the way, similarly here for the continuation monad, I have spelled out the continuation monad for R explicitly. Yes. Good. So then we go to look at some other applications of this interpretation technique. And I'm going to show two. So here is a get out of Jill Freekart that you might be lucky enough to be able to use at some point. Let's say you have two pieces of code. We have some F and some G. And F wants to call function G. And what we know for F is it's F is working in an abstract monad IO. And it wants to call another method G. And G also works in a monad IO, but it requires an additional constraint to be satisfied, namely that M is a monad fix. Well, at first instance, well, F cannot call G because the compiler would complain no instance for monad fix. Interpretation can get you out of Jill in this case, can help you save the day. Because what you can do is you can instantiate G, the method G, to the IO monad specifically. And the IO monad specifically, we do know that it satisfies monad fix. We have an instance for monad fix IO. And then this concrete, this instantiated G can be interpreted into the abstract monad IO. So this is to say that it just so happens that for the concrete monad IO, we have more instances available than we get from the overarching type class monad IO. And so this, in some sense, allows you to conjure up type class instances when you're somewhere, maybe it was not possible to rewrite F because you don't want to add constraints here. But in fact, you don't need to rewrite F. You don't need to add any constraints because by interpreting G after instantiating it to IO, you don't need any additional constraints. So that's one application of this interpretation technique. And another one is about writing Haskell errors. Now, I know that errors are not used very widely in Haskell. And there's a debate on whether or not it's a good idea to use them in the first place. But it's a fact that they have important applications in Haskell. And so I don't want to say anything more than that. It's a fact that there is arrows based code. And so this is just a tool in the toolbox. So just like monads are usually written in terms of with the syntactic sugar of monadic do syntax, Haskell errors are usually written with the syntactic sugar of the arrows language extension. And here on the screen, you can see some of it. And as you have already probably observed, it's quite a difficult and verbose syntax. Now, the trick here is that using interpretation, we can avoid using this arrow syntax while still writing arrows. So even if you've never seen a single arrow syntax, you already know enough about monads in order to write some Haskell arrows. So we can in fact write some Haskell arrows with monadic do syntax. And by the way, I don't expect you to understand what this code is actually doing here. This is just to highlight the syntax. This is just to highlight the verbosity of the Haskell arrow syntax. So this code can be written in monadic do syntax like this. And having written it in monadic do syntax, well, of course, we're going to get a monad. We're going to get a monadic effect. We're going to get code out with a monad effect. But this monadic effect can sometimes be interpreted into abstract Haskell arrows. And to clarify, this can be done to build Haskell arrows that are not instances of the kliasd class, so non-kliasd arrows. So non-monadic arrows can be written with this technique. All right, so that's two funny applications of this interpretation technique. So next, I want to work towards understanding when what effect interpretation is from a mathematical point of view, and in particular to understand when we could consider it lawful. Are there any questions so far? No, there aren't any new questions. Right, so let's jump into some mathematics. And I want to start with a citation from Andre Bauer, or a paraphrasing by Andre Bauer from Phil Wadler. Monads, as a programming concept, would not have been discovered without their category theoretic counterparts. But once they were, programmers could live in blissful ignorance of their origin. So today I want to say, let's look at the origins of Monads for once. I think there are some lessons to be learned even from the original work on Monads in computation. I have to preface this entire discussion with the disclaimer that I'm going to set aside the question of whether there is in a true sense a category called Haskell, and whether or not you can truly do category theory in Haskell, let's glance over that discussion. So the key concept to underlying effect interpretation is that of monad morphisms. Monad morphisms are the morphisms of category of monads in Haskell. So I want to spell out what monad morphisms are in detail, although I'll say some things about it later on. In fact, it's not really important. The key thing to know is that monad morphisms are certain polymorphic maps like this that have to satisfy some conditions. So one is naturality, that they're natural transformation, and then there are two more conditions that essentially say that they're compatible with the monadic structure and on end. So the interpretation functions that we've seen before are all monad morphisms. So here I've copied a bunch of them. So lift IO is an example of a monad morphism. Lift either, and lift are also monad morphisms. And then finally, generalize, and I want to focus on generalize because it satisfies a particular property. Generalize is the only monad morphism from the identity monad. So there is no other monad morphism that you can define out of the identity monad. Or put differently, every other monad morphism out of the identity monad is provably equal to generalize. And it works for every, like it's a monad morphism to any other monad. It's not restricted in its co-domain. So to spell that out in category theory terms, in the category of monads, whose objects are instances of monad, so the objects are Haskell monads and whose morphisms are monad morphisms, identity is an initial object. So this is spending out the fact that from identity, there's exactly one monad morphism to any other monad. And in particular, this one monad morphism is found as generalize. Generalize does the trick and it's the only thing that does the trick. Okay, so that's kind of a nice characterization of generalize as an interpretation function. But what about our other interpretation functions? Like can we somehow similarly characterize them in terms of category theory? So for instance, let's look at error interpretation. So what can we say about monad morphisms and monad error? That's supposed that we have two monad error E monads. We have M and we have N and they're both implementations of monad error E. When is a monad morphism between them compatible with this monad error structure? When does it somehow preserve the errors? Well, a monad error instance is defined by two type class members. So there's throw error and there's catch error. So somehow any monad morphism between monad errors better be compatible with the respective implementations of throw error and catch error. So here's one way to make that precise to say that throw error composed with T. So this is the throw error happening in M is exactly the same as the throw error that comes from N. And similarly for the catch, oh, sorry, this should say catch error, not catch. I'm sorry about it. Similarly for the catch error method, so whether we do T on the entire thing or whether we do T on the arguments that should be the same thing. And so if these conditions are satisfied then we might call such a monad morphism T. We could call that a monad error, monad morphism. So T is a monad error, monad morphism if these two additional constraints are satisfied. So with like having said all of this, I can say the magic property of error interpretation. The characterization of lift either is that it is the only monad error, monad morphism from either E. It exists uniquely as a map to any other monad error E. In other words, lift either is the only monad morphism out of the either type that is compatible with monad errors. Again, phrasing this in some more category theoretic language in the monad error E category whose objects are monad error E, monads and whose morphisms are monad morphisms compatible with those errors either E is an initial object. So this I think characterizes our notion of effect interpretation that there are certain initial objects in categories of monads. So maybe I make a pause here for questions even though it's not the end of the section because next I will just dive into more and more details of mathematical category theory until I run out of time. So this would actually be a good question to ask questions. We have one question and yeah, this is a small reminder that we're just five minutes to the end of the session. The question is, is uniqueness important here? So uniqueness is not, I mean, in Haskell land, it is not something that you observe. It's not something that is somehow visible on the Haskell side. For me, it is a motivation that the uniqueness expresses that in some sense, so for either here that either is the most basic and the most fundamental representation of errors. You could also consider other implementations for which this uniqueness property is not satisfied and then there would also be a valid notion of interpretation but then you don't have, so once you let go of this uniqueness, then there are choices to be made on which monad morphism you choose. And that means that in general, there could be a behavioral difference at runtime on which interpretation you choose. So this is, so, okay, is it important? Well, it means that there is, so when you have to see uniqueness, then it's a thing that you don't have to think about. It's a guarantee that you have, it's, yeah. So would I restrict interpretation to only those cases where we have the uniqueness? No, I think interpretation makes perfect sense also when you don't have the uniqueness, but when you do have the uniqueness, I think there's a more clear mathematical modeling of what we're doing. I hope that helps. So if there are no further questions, then I will continue just diving deeper and deeper into mathematics. So now I want to show a little bit of the history behind monad. So here's the definition of a monad in the original paper that introduced monads to functional programming. It's from 1989, this paper. And here's definition, 1.1 defining a monad as a triple T eta mu. And this eta is, it's what's in Haskell is called the return operation and mu is in Haskell, it's called the join operation. And the point here is not to understand the mathematical symbols and the details of the notation here. It's just to show that the monads essentially in essence are a very old concept. And yeah, the Haskell version now looks something like this of course. And I want to talk about this because well, what I want to work towards is that also monad, so I think this is not broadly known that monad transformers are also not such a new idea. In fact, also already in 1989, monad transformers were being considered and I'll show that in a few slides. So first of all too, so one of the main things that Eugenia Moghi was doing in this paper is explaining concepts in functional programming in terms of category theory and pure mathematics. And so if you want to do, sorry, was there a question or? Yeah, actually this reminded that we are at the end of our time, so can you wrap up the session in a minute. Sure, let me very quickly show you some one or two final slides and then I wrap up. So I quickly show you that here is Moghi's representation of monad transformers also already from 1989. And what Moghi then continues to look at is where are these monad transformers themselves have some kind of object, have some kind of category theoretic meaning. And that is when he's led to the question of when a monad transformer is a functor and or a monad itself. And that leads us to the Morph library in Haskell. So the takeaways of the talk is that we can optimize MTL based codes pretty much for free without much elbow grease. And sometimes it's a lot and often it's just a few percent and it's very often in the premature optimization domain. So I definitely wouldn't recommend anybody to apply this kind of technique too broadly. I don't wish to advocate for or criticize MTL or extensible effects, algebraic effects, handlers, et cetera. No, it's just a two in the box and let's talk about let's give the tools in our box a name. And this tool is a compatibility between MTL based code and code based on concrete effects that also as an alternative application allows us to fulfill type class constraints that are not in scope. It allows us to write arrows with monadic do syntax at least sometimes. And I think in general it shows that we shouldn't think of the question of MTL, shouldn't think of MTL and transformers as MTL library and the transformers library as an if or else like it shouldn't be a versus but it should be end. We should combine thinking in terms of concrete monads and thinking in terms of monad classes. I think it's important to understand the category of monads better and even if surprisingly for the most part the MRF library is not central to this question. And with that I guess I wrap up. Thank you very much for your attention and the good questions. Yeah. Thanks a lot for your time and the wonderful session.