Devreal

Hands-on Category Theory

Event: Silicon Valley Scala Symposium

funconf 2013, James Earl Douglas: Hands-on Category Theory

Recording: funconf 2013, James Earl Douglas: Hands-on Category Theory

so uh this is actually really cool to see everybody here um this is going to be a slightly different kind of presentation very experimental so I hope you'll bear with me uh this is the singular slide from this presentation and I'll spend the rest of the time sitting here uh banging away at code so if you can't see me uh I'm still here I'll just listen to my voice and watch the terminal so this is going to be Hands-On category Theory uh that's maybe a little bit lofty of a goal uh we're actually going to focus very specifically on how to do functional validation so you may be familiar with validation from Scala Z or other types of validation Frameworks where you can take values such as parse adjacent values or something and then try to pass them into your your server-side code for example and then do things with them and you might have invalid or valid conditions that come out of that so we're going to sort of dive into how we can build up a library that lets us do all that from scratch but before we get there if anyone wants to follow along or write the code as I write the code you can go to this GitHub project Scala cats under my GitHub account and you'll see basically the exact same tutorial I'm about to walk through but it's all spelled out and so quickly before I jump into the terminal uh just to get a better idea of where we're going uh we want to be able to write code that looks like this so we want to be able to write a parser that can return a possibly valid or invalid value and then write a pure function that only operates on pure values and knows nothing about validation but be able to map that function onto parse values and apply it to additional parse values and so to get there we're going to look at things like functors Center groups and applicative functors we're also going to cover a few feel free to stop me at any point there's going to be a lot of interesting Scala features that we sort of touch on but I won't spend much time covering in depth but things like algebraic data types type lambdas higher kind of types implicit conversions type classes and so forth cool so let's put this to the side yeah question so the fact that you're using some some special symbols for for division and the multiplication oh uh so the so the question is about these symbols these are this is not meant to mean multiplication or division these are just uh operators that uh have kind of a funny syntax and the syntax is inspired by Haskell and some other places but uh we'll we'll see later that this actually means map and this means apply from functors and applicatives all right so I'll just put this to the side we might come back to it if I need to copy paste any code and I want to make sure the font is going to work for us um so let's actually remove that start from scratch okay so is that visible at all no okay let's uh try changing our um color scheme here something a little bit more readable um anyone know OS X and why that didn't work is there an apply button oh gotcha okay how's that a little better cool all right so let's start at the start um imagine that we want to build something like a web service where we're trying to bridge two worlds uh between our our clients who are living across some HTTP Channel and our server where we have full control over all the code we write and in an Ideal World we have very nice clean pure referentially transparent server code but our our web world is uh not so constrained and so since our clients are going to be accessing our services over HTTP there could be all kinds of problems with their method invocation so they could send bad data or invalid data or malicious data or even no data and so these are all cases that we have to handle but we want to figure out some way to cleanly bridge the gap between these two worlds so that this the process that we end up taking for validation doesn't sort of leak into the server code that doesn't care about validation so let's start with a very simple imaginary method that we want to expose as some kind of a service to some unsafe world so we'll write an absolute value function so absolute value has a following type so absolute value is a function that takes in it and returns an INT and for implementation we'll just steal it from Scala okay and hopefully I can have at this resolution a couple of Windows side by side we'll see if it works I may want to do them vertically actually try this oops and see how that works for us so in parallel I'm going to be editing this text files cats.scala and then interpreting it in The Scarlet Rebel so there's our file and we'll run Scala and so I will periodically be loading this file to make sure it compiles and then to also interact with it cool so there's our apps function so we can test it out with a negative number and so that seems to be working so now let's write so imagine that we want to expose this to a world where data is potentially invalid so let's write some sort of parser that can take data from that world and then return something that's a little bit safer to use um so we're going to write a parser so this takes a string and it needs to return something that can be either valid or invalid so we'll use skull as either type for this and either uh if you're not familiar with either either is an abstract class that has exactly two implementations called left and right and there's really nothing special or magical about them they're they're just boxes that hold a single value and are distinguishable by by type it's either a left or it's a right and that's pretty much where uh the the specialization Ends by convention when we're using either to represent things that can fail or things that can be valid or invalid we tend to use left to wrap invalid values and write to wrap valid values but again that's just convention and it really doesn't matter but for this demonstration we will use that convention so on the left uh if if our input fills to parse is an integer we'll return some string containing a message about the failure otherwise we'll return the successfully parsed integer and we'll do this with a simple try catch so we'll say x dot two int and so this is returning an integer but up here we see our function returns in either so we have to wrap this in a right and x.2 and could potentially throw an exception if for example we tried to call to it on a string that is not parsable as an integer it will throw some kind of number format exception so let's just catch any kind of exception and say uh if any exception is thrown or any throwable is thrown we'll just say well we don't know what to do thanks all right so let's make sure my syntax is correct so now we've got two functions abs and a to I and so I can call a2i like this pass in a string and since the string 1 0 parses as the integer 10 I get back a right containing that 10. uh if I were to pass it something that is not parcable we get back a left containing the message 10 is not an integer or at least as far as x.2 understands so now we have a way to take input from the user where that's represented by a direct call to this a2i and get some sort of uh some sort of output that might be valid or might be invalid but we don't have to worry about any exceptions being thrown or anything like that we also have a service function which is a pure function end-to-end that we want to put together so we could do this with a pattern match we could call a to I and get back in either like we did here and then pattern match on that and if it's a write pass it to the ABS function and return the result or if it's a left just return the fact that it failed that's a little bit more verbose than we'd like we what we're really after is a single line way to apply one to the other so what we actually need is a functor and so you're probably whether or not you know you're probably familiar with what functors are if you've ever done something like this if we have lists of some integers and you call the map function on that list and you pass it some function that takes an integer and returns something so we'll just say X plus one uh the the instance will take that function X plus one and apply it to every element in the list and then return a new list containing those elements and even though Scala doesn't have a trait called functor it's basically the same shape and so we're just going to make it more explicitly declared here so let's start with the trait called functor and it's got a couple type variables which I'll come back to in just a minute so a functor is anything that defines the following method for our purposes so it's a map function that takes some function from A to B and returns a new F of B so what's going on here we have a trait that's abstracted over two types A is pretty simple a is just some arbitrary type f has this syntax which means that f is some unary type Constructor it's a type that itself needs another type to emit a type so for example basic Scala collections like list or option or set or whatever are each things or each type Constructors that take a single type so I couldn't uh really have a list of something like this because you know if we want our if we want to forget about type Erasure and job before compatibility there is no such thing as a list it's always a list of something so we would say a list of integers is um and it'd be nice if I got my syntax correct so if I say Val X is a list of integers and then I give it some value right so in this case list is corresponds to our F because it is a type Constructor that takes a single type variable so then when we Define our map function we're basically saying that there's some type Constructor list for example that contains things and in this case it will be a thing of type A that we can pass a function from A to B and then our output will be whatever that type Constructor was but with the B type so in this example here we have a list of integers and so you can imagine f is list and a is int um and we're passing in a function which is just int to int and so our output is list event where B is int and so as I said we want to uh we're playing with the either data type here so we have our parser function which returns an either so what we need to do is build uh an either functor and if you're following along on the GitHub page you'll see some diagrams that show sort of how you can imagine the categories of types and the categories of lifted types and then what map is doing when we're transforming a function from one category to the other it's a little bit too hard to describe with words so I'll just skip it and allow you to read that later but let's write our either functor so I want to just to make this easy to use I'll put it in an implicit conversion so that we can automatically add this map function onto any instance of either okay so this is an implicit function that takes any either um and the reason I'm using these type variables is we will want to use the a later for our A to B function but Z is sort of our error case it's our invalid case so it's not going to change and you'll see you'll see that demonstrated shortly so our implicit conversion takes an either and it returns a new functor of type A and then here it starts to get actually a little bit tricky so as I said we need a functor has these two type variables one of them is a type Constructor which itself needs a single type either is a type Constructor which needs two types so it doesn't really fit the shape of the problem that we're trying to solve but as I said we with validation our error type doesn't really change over time but our success type might change just like with list.map you could get a new list of uh strings or a new list of something else that wasn't what you started with so for example we had this function before we could just say x.2 string and a result so we went from list event to list of string and so we'll do something similar with either we know Z will never change so what we really want is to constrain yeah you're saying that the failure does not change if you change the types and kind of exceptions that you're gonna get is going to be different or you're abstract and failure we're abstracting failure as a left of whatever we started with so in this case failure is always a left of string and we have some will the exception could trigger the failure but the way we're representing it from our parser is would be a left of string oh right as long as it once we've got a left we have a failure so there's really nothing we can do further so we'll always stay a left of string yeah yeah so does it feel a change in any of the consecutive the failure uh the failure will not change type but you'll see when we get to applicatives that it will change its value and we'll see how that works in just a minute okay so what we really want to do is constrain Z so say our our F type is always in either of Z or something where something can change Scala doesn't allow this syntax so to get the same kind of thing we need to use a type Lambda and the way we do that is declare a new type so we will call our new type either Z and give it an arbitrary type variable so an either Z is an either of Z oops or B and then the way we reference this thing is with this hash Z so this is a little bit noisy but what it means is that our F of something is really and either of Z or something whatever something might be so a functor defines map so we'll do that so remember map takes a function from A to B and returns whatever uh whatever type we're lifting into a functor so in our case that's in either of Z or B so we're basically adding a function map that given an either z a and a function A to B will give us an either z b so if we have a left yeah all right so the question is can we move the type Alias to clean up the code a little bit I think we could put it in an object somewhere but I'm just declaring it in line here for for demonstration all right so uh we know that our our X isn't either so it has one of two types it's either a left or a right so let's do a pattern match so if we're starting with a valid value a write um then we have some value uh actually let me make this more clear we have some a to which we can apply the function f so we'll return that in a new right on the other hand if we're starting with an invalid value there's really nothing we can do with it we have a z for some type Z and we have a function A to B so we can apply that function to the Z really the only thing we can do is pass that thing directly through so we'll just return a new left of the same thing right I could write an at sign right I could do something like that same idea so let's make sure that this compiles oh yeah okay so now we've got an ether functor which is a way to add a map function to any arbitrary either question um the question is for this type Lambda we're creating an either Z but the thing we're returning is not an either Z well it sort of is but not by name it's an either of ZB really that's just a side effect of how we've chosen to Define our functor trait so our functor is it could return a functor it could go from functor to functor in our case we want to go back down to the either type because that's where we started and that's kind of where we want to end up and then we'll since we have our conversion we can lift that back up to the either functor if we need to to apply map again uh so that kind of a non-answer really it's up this is just the one way to implement it um Scala Z does it slightly different where their functor type declares um something like this I think rather than in the trait uh uh it's just two ways of scanning a cat define equivalence so I'm not sure if the type system would consider them equivalent for like a DOT equals or an equals equals but semantically they're equivalent I mean it's just a type Alias so I think like in Scala if you created type Alias Foo equals string Scala considers a foo to be a string but I don't know if it considers a string to be a foo I'm not quite familiar with that yeah you can't imagine you can't instantiate the Alias okay so maybe I'm not sure but let's press forward um there's uh um you can see this thing in action so what we want is an either that we can create and turn into a functor and then uh lift up our apps function and then apply the two so let's parse an integer actually let's make it negative so we're actually seeing ABS work so there's our either and if we call map now we will invoke this either functor so that we're pimping the map function onto that ether and then we can pass it so we have a an either of string int so we have an either of string int so our a is an INT so we can call map and pass a function from int to whatever we want so we'll pass abs and so the result is to lift this function f of a to B into the either functor so that it can be applied to the value that's inside or not if we started from an unparsible value like this and then we try to map our absolute value function we would just end up with the same error message and so it's kind of a no op and that's because we matched on this second case um and then one further thing before we move on to applicatives is uh it's sometimes useful and it will be especially so more toward the end of this talk um to be able to do this in Reverse so we're starting with an argument uh that is uh an ether and we want to lift it into an either functor so that we can apply uh a function so that we can lift a function and then apply that argument uh to that function uh it's sometimes nice to be able to do that in Reverse so we start with the function and we implicitly lift that so that it's ready for an argument that is contained in either and then they can be applied so let's write that code level of validation yes yes the goal is to do validation um but to do the goal is to do validation but to keep the validation logic completely separate from our server logic so I haven't modified the absolute value function to understand validation it just takes an integer it doesn't take an either string end that's really the key and then later we'll be able to compose these things together but still without worrying about validation on the server side so we want to be able to lift any function such as absolute value into something that has this funny looking method so all this is doing is the what we did below but we're starting with the function and we're lifting that up before we have the argument to apply let's make sure I type that right cool so uh we had this construct before now we could do just syntactically the reverse of that so I can say absolute value and then map that function onto a parse value and we get the same result so any questions before we move on to applicatives yeah by that functor we said f and then underscore right in the bracket would specify there the capitalized d because we know that the time uh can you repeat that I don't quite understand oh whoops yeah right there trying to set up the underscore and underscore could you specify the a in there because the time I see so the question is can we say F of a right here uh no um to say f of a would mean that we're always using an either string int but it's possible that the the result of mapping could change the type of that either this is the type Constructor to use so F of underscore is not quite yet a type it's like list yeah but the under the underscore this is just the Syntax for saying we want to use the type we want to use f f which is a type Constructor of a single type variable this is just skeleton tax for for saying that um so this is just a way to tell Scala that f is a type variable but by the way it's a higher kind of type where it takes one type variable what's that uh it could be I don't know templates okay so I'm going to comment some of this out since uh we're just in the rebel I don't have nice namespaces and then I'm going to quit the rebel and start it again and that's just because I want to clear out the implicit conversions from memory okay so let's look at a slightly more complicated function this will be an addition function between two integers so this is a function that takes two ins and returns another end could be straightforward okay so the function add to takes an X and A Y and it Returns the result of X Plus y uh if so if we want to follow the same kind of process where X and Y are both inputs provided by a client and they they need to be validated they could be invalid values uh this starts to get much more tricky because uh we would Now call a to I on both of those inputs and come out with two ethers so we would have X which is some parsed value which might be a right or a left and Y which is another parse value which also might be a right or a left and so we can't call map on one of those and apply add two because we lose track of the the second one so a functor won't quite get us where we need to go we need an applicative functor so an applicative functor Builds on a functor by adding a new function called app so let's actually I'm going to go up above here so here's our functor trait uh an applicative it looks almost the same and it extends functor and so to build on your question before this is how we tell Scala that our F of underscore is this F of underscore so in this case we don't have to pass the underscore it's just an implementation detail of the semantics of the syntax could you really oh okay I don't think I don't think you could do this I think that would not compile because it wants a concrete type and so our concrete type is this type Constructor I could be wrong I also have no idea what I'm doing so um cool so let's build on our functor we already have map because we're extending functors so let's add app and by the way the names of these functions are largely historical in nature so app looks almost the same as map with a key difference rather than taking a function A to B it takes a function that's already been lifted into the functor of a to B and still returns an F of B the same kind of type so map and app return the same thing and they start from the same place but the function the argument that they take is either a function in the category of types or a function in the lifted category of ethers in this case and we'll see whoops I need to be there we'll see what that looks like uh in just a sec so there's actually quite a lot of code for me to type here and I'm running low on time so I'm going to cheat and copy and paste it and then I'll go over what I just did oh um so actually before I get there we want to uh since we're we're now parsing two input values and then potentially producing a result there could be potentially two error messages right if both X and Y fail to parse we would want to know about both of those failures and so think if you're building a web service and maybe you're interacting with Json or you're taking a form submission from a user you want to be able to to validate that whole thing all at once and then know everything about it that failed and not just know a binary State like either it failed or it's good to go so we're going to change our either type a little bit to be either a list of all of the error messages all the things that went wrong or the successful final results and so this doesn't change much this is just we we insert a list there foreign but before we Implement our either applicative we also would like to keep this thing nice and generic there's there's nothing in uh inherent in applicatives that should bind us to scala's list API uh so we'd like to generalize the notion of things that can be appended so a list is something that can be appended I can take one list and concatenate it with another list with the plus plus operator but that's really a gen general property called semi-group so semi group is any type that that can be appended to itself and return the same type so a list appended to another list gives you a list for example so if we take an a we should return a new a and so let's make a list semi group so that we can use this append syntax generically instead of list dot plus plus or something very specific so we want to be able to convert a list of A's into a semi-group of a list of A's and so that way we can Implement append which takes a new call this x a new list of A's and Returns the concatenation of the two make sure that compiles looks good so what does that look like if we have a list of one two three with Scala we could say plus plus list four five six but with this implicit conversion we can also say now append and append will take our list convert it into a semi group of lists which now has an append function that takes another list and returns a list so really a pen is kind of like a synonym for plus plus in the list case and then you can imagine other things that would be appendable Maps or arrays or you know any kind of collections or even a string might be an appendable sequence of characters but for our purpose we just need lists of strings for our error messages okay so now I think we can yeah now we can build our either applicative and this one's pretty long so let me just copy here save time and then I'll go over what we're doing so if you remember I'm gonna go to the top here an applicative is a functor with this extra function app so if we look at our either applicative we Define map and that's actually the exact same implementation as we had before but I've commented it out so there's no code sharing but but explicitly we have the same kind of construct we have the same type Lambda that we saw before so we need to constrain that Z-Type which is now going to be list of string instead of string as it was before and we're lifting up an either where it has some error type which in our case is list of string and some data type that we'll be manipulating we also need to add this implicit here we don't want our parser to worry about semi groups directly we just want to deal with things that can be turned into semi-groups so our parser returns either either list string or int but for us to be able to use a list of string as a semi-group we have to also pass in an implicit conversion for whatever that Z-Type is into a sending group and so that way later if if I am working with a z down here in this code I can convert it into a semi group and call append okay so I'll skip over Matt because that has the exact same implementation we had before and let's look at app so what have we got here we have uh a parsed value that the user passed in so that might be a right or left and then we have some function that has been lifted into an either so this could be a left of a list of error messages or a right of a function that we want to apply to the input if it's valid so we actually have four conditions we need to worry about X can be left or right F can be left or right and we need to have the take care of all four combinations so let's first look at x x is our input value that was returned by a parser so if if our parser gave us an error so it gave us a left with a list of error messages um then we know our output is also going to be an error because our our data is either valid or invalid and once it's invalid it will always be invalid so we look at our uh the function that we've lifted which itself could be an invalid or a valid elect or a right and if it is also a left we need to take the error messages it knows about and append those to the error messages that the parse value knows about so that we're not losing any data so we if that's the case we return a new left with all of those error messages appended together and so this is where our semi-group implicit conversion gets utilized if we were explicitly using lists throughout this and maybe I should have done that just for Simplicity for this presentation but we could have just said L plus plus L2 if these were lists but as it is L is just any generic type that can be converted to a semi-group if on the other hand the function that we want to apply is some valid it's a right of the function that means we have a right of a function A to B and we have a left of a list of messages there's really nothing we can do we can't combine these in any way so we just pass our x value which is the left of L directly through there's there's nothing we can do so we kind of drop the function on the floor so that's if our input value the thing that was parsed is invalid if on the other hand it's valid meaning it's a right we we might be able to use it so f is the function that has been lifted F could be an invalid value it could be a left in which case we're in the same situation we have uh but with the types reversed we have a an INT in our case and a function sorry and a left of a list of messages so there's nothing we can do with that int that's meaningful so we just pass through that same list of error messages finally if we have an input value that's good and a lifted function that's also good we can apply it which is what we really want so R2 is our function from A to B it's this guy and so we we take that function and we apply it to the valid input value and then we re we evaluate that and then return it all in a right so that's kind of a mouthful and uh a bit of a fire hose but let's see if we can put that to some use so first let's look at oh we already looked at the list semi-group so let's look at the either applicative so we have our add to function which takes two numbers and adds them and we have our parser which takes a string and returns either the parse value or an error message so how can we put all this together to parse two input values and then send those off to our add to function and then maybe get a valid result well it looks a little bit like this we we parse a value and then we map it onto the add to function and what does that give us that gives us this kind of complicated looking thing it's an either where it might be the list of error messages or it might be a function into int well what the heck is this function that means that if this thing parsed correctly it partially applied add to with the value 22 and the result of that would be another function that's waiting for y so that it can add 22 to Y and then return the result so the result of calling this is an either that might be a list of error messages or it might be more code to to execute and so we can we can send another input value off to that function if it indeed is a function with the app method so let's parse another value and call app and then send it off to whatever this thing returns and so there's the result of 20 plus 22. and so if either one of these were unparsible we would get a list of all of the errors so in this case there's one error a is not an integer and if we had two errors we would get a is not an integer B is not an integer so you can imagine if this were being applied to something more complicated like form validation or Json parsing we could collect up a list of everything that's wrong as far as our validator is concerned but we're not quite done yet because this syntax is a little uh a little bit tricky and you know we have to get a parenthesis right and we have to know that because of the uh the way that applies you know it must take something that is in either of list of string and function so it's it's kind of confusing to get the order right furthermore the function is that it appears last in our syntax which is maybe kind of annoying it might be nice to have it on the left yeah using this is scholar 210 yeah let's talk afterward because I'd actually like to see what two nine chokes on um so we're going to go back to that funny percent operator that we had before that let us turn functor around and add another one uh that lets us turn app around as well so we'll have uh basically what we had before but slightly adapted to know about semi-groups so this this lifts a function G to B such that we can apply um we can map it onto some input value with the Restriction that Z must be able to be turned into a semi-group that's about right you just double check that compiles yep all right so that's basically the same mapper that we had before uh except we've added this implicit conversion so that we can use these semi-group Z types so now let's do the same thing for the app operator so this will be a little bit more complicated but not terribly so in the case that we have an either Z or a to B like we had Midway through that process that we saw before and we have a way to use a semi group then Define a function called this thing which is sort of a I don't know an asterisk in in angle brackets and that that takes an input value that has been parsed which might be applicable to this function and let's double check that compiles cool so what does that give us well the last thing we did was this oh sorry let's use some actual numbers the last thing we did was parse two values take the first value and apply it to this thing that's returned by mapping this parse value onto the add function now we can turn the whole thing around and say add to and then onto that function map the value returned by parsing the string 20. and then onto that function apply or app the last value and there we go so we get 42 just like we did in this case there is a key difference though so let's look at again unparsible values so if we have a and b we get a list of error messages a is not an integer B is not an integer if we use this syntax A and B you'll see that the error messages are actually reversed so we have list B is not an integer a is not an integer and that's simply a function of the order in which the application is happening because of these conversions but it is something to to take note of and so finally uh you know I mentioned composition at the beginning so now we we have sort of a nice clean way to do um both validation and application together and then of course if this were an actual web service I were writing I would have logic that would take the result of this and if it's a valid value if it's a write send it back to the user and if it's a left you know build some error message and send an HTTP status code 400 or something like that but as it is uh we have a a right or a left as the output of this but we can also start to compose these things so let's say we want to call add to and abs together so we want to say add to and then on to that map the result of mapping ABS 1 or ABS onto a parsed value and so this should give us an either that's ready for the next argument just like we had before so we can say a to I of 22. and there we go and so now we can start to compose these things and it stays somewhat readable as long as you remember that this percent operator which could be more appropriately named uh means this is something that needs to be mapped and this star operator means this is an already lifted function that needs to be apt and I think that's about that's about as far as I wanted to take that so are there any questions so you're talking about scholazi's applicative Builder so the question is scholazi's applicative Builder lets you build up the applicative and then call app on the result and I haven't I haven't worked with that so I'm not quite sure but it uh with applicatives you need to call app every time you want to apply a function that has been lifted so I'm I'm not quite sure how that works in particular it sounds like it might be similar to this syntax but I'm not quite sure scholazi does this slightly differently as I touched on where the functor and applicative traits are defined with an explicit starting type so you would say rather than saying a functor is anything with a map function in scholar Z they say a functor is anything with a map function that takes the type constructed value F of a and the function A to B and Returns the F of B so it's slightly different and it's actually a little bit better in the way it constrains what you're turning into a functor but I like this as a way to reason about what the functor does which is map over things do you have any uh kind of production examples of applying this kind of technique so examples of applying this in production we use uh we use something similar to this to do validation on our web services where we have multiple functions that might succeed or fail that need to be composed like this and by composing them together we let those we we get to lift these pure functions into this validatable world uh without having to do lots of pattern matching and if right than this if left than that that's all kind of abstracted from our server-side code so I can't point to anything on the web that would explicitly demonstrate this but it is in use basically this is directly applicable as is on any kind of rest API or or anything that takes unsanitized input very very valuable to see yeah I agree so the question is it would be valuable to see a non-trivial example I I wanted to take this all the way to a web application but unfortunately that would take far more time so we have this simple um trivial example yeah is there a connection between applicatives and concurrency yes one thing that may not have been clear is that this a2i and this a2i invocation are not dependent upon each other they both produce an either as a result so they could actually run in parallel and this whole line could be deferred until all of the results are there we could also make it a little bit smarter I I suppose that if the first failure that occurs might and the execution of the others I'm not sure I haven't really played with that but but certainly the evaluation order does not matter of all of these arguments and so that's slightly different than something like a monad where you have to do the The Next Step depends very specifically on the result of this step um the percent operator is associative the this star operators associative because that's our our apply function percent let's see so percent is map which we would really I think we would only call that once in this example so it I'm not sure I can't think of an example where we would have two oh so you're talking about uh this one yeah let's see that's a good question so actually not quite um so it's actually this operator that's uh that has associativity I think um and then because remember we have to do all of this and then finally when we have our our lifted values can we apply it to the function so this is actually happening last we're just writing it first because it kind of makes more intuitive sense so I think I'll take any other questions uh during the break um because I'm getting the the red light so thank you everybody foreign