 on the schedule, you would know that you're in store for building a better future, advanced air handling for concurrent programming with ScalaZN, shapeless. What a terrible title, so long. What kind of jerks came up with this? I'm jerk number one, Eddie Carlson, how's it going? And I've been working at White Pages for about two years and building highly concurrent search systems for them in Scala. And I'm jerk number two, Jean-Henri Desjardins, back-end Scala engineer at White Pages and also open source contributor. Yeah, so the topic of our talk has shifted a little bit since we first made that title. We've focused it up a lot and we've done away with that long part after the colon and stuck with building a better future. And we do want to build a better future with you, one with sunshine and rainbows and kittens. Okay, that's all super cute and nice, but what do I really mean by that? Okay, so what I'm, oh, what I'm gonna show you. Yeah, meow square bracket, left bracket. Anyways, what we're gonna show today is how to sequence futures in a well-typed, fail fast manner while preserving order. And we think this is something that is not possible with the current tools. At least we looked everywhere, I don't know how to do it. So I think it's new and on top of that, the, our implementation for this, the actual code is fairly small and straightforward. So it's not the actual implementation that's interesting. It was our journey to learning where and more specifically why we had to place this code where we did in order to make this work. And yet we did use Scala as the end shapeless in order to accomplish this. So that's what we're gonna show you. So to motivate this, why do you wanna sequence futures? Maybe everyone here already knows this, but here's an example of a small search system like you might see at white pages. So the system will take as an input someone's name and then concurrently it will launch three requests that each return futures. One a future for a phone, for a home, and for a home address perhaps, and then one for some work information. And in order to prepare a response, we're gonna need to wait for all of these futures to complete and then use the value contained in the future to prepare this response. So we want some syntax sort of like sequence, phone, home, work, then we can extract those values and prepare a response. And there are a variety of ways to sequence today. In an earlier talk, we saw future.sequence. And future.sequence is great. You can give it a sequence of futures like you see here. And does anyone know what this future.sequence call here is gonna return minus the map. We give it a sequence of future of any and it's gonna return to us a future of sequence of any. And we're gonna fail our linter test if we have our good testing from yesterday. Cause it's gonna infer a sequence of any. So yeah, this future.sequence is nice and great. It's good to use when you have a lot of futures that you want to wait on. But yeah, there are a couple of problems. There's some limited typing. You can see here in my match statement I had to put in P as phone, H as home address, W as work info. And that's bad for a couple of reasons. One, if I screw up the ordering for some reason, maybe I'm just dumb and I can't get it right, then it will still compile and it'll look fine but then I'll get a match error at run time. Another reason is if my type names are very long or if I have a ton of them, it becomes unwieldy to work with. This line could be super long. Maybe I need to put on multiple lines and it just looks ugly and terrible. Another reason this isn't so great is that this does not fail fast. So imagine that the home system or the home service is down and we send off our requests for phone, home and work and phone takes two seconds to complete instantly but home has an exception right away. Well, in our sequence here, we're gonna have to wait two seconds for the result of future.sequence to be that exception. Even though home has failed immediately, the desired behavior here would be that this future.sequence fails instantly or not instantly but as soon as home has failed, as soon as any of them have failed. So the ultimate goal here is we want an interface sort of like this where we can provide multiple sequences, sorry, multiple futures to a sequence function that's going to fail fast as soon as any of them fail and also will retain order and type information so I don't have to plug it in there. There are also other ways to sequence. There is the for comprehension. I take the P out of my phone future, H out of home future, W out of work future and you can see it does have better typing here so it solves that one problem but it cannot fail fast. We know the for comprehension is implemented with flat map and map and the flat map definition is very much at odds with failing fast because in order to move on to the next element or the next computation in a flat map, you need the result of the first one. So going with the for comprehension is not going to work for failing fast. Oh yeah, here's the function definition for flat map on future. You see on a future of T, you must have the value of T before moving on. So fundamentally opposed to failing fast. Third way to sequence. We heard a little bit about this yesterday, I think, is async await and it's a newer sort of deal and the idea behind async await is to provide a way to write programs that look sequential but are actually doing concurrent things in the background and you can't really tell from this example but it is really nice. It allows you to clean up a lot of your code because the flat map and map isn't littered everywhere. You can grab a value P from your phone future just by awaiting and then you can use it as if it's of that actual value. So it's really nice but we have the same problem where it does not fail fast. Yeah, so it has the same properties as the four comprehension here and this is all implemented with macros. I have no idea what is going on behind the scenes. I don't know if this could be improved to fail fast. I don't know what's happened in there. Sorry. Anyways, yeah, so those are some main ways to sequence today. Okay, so Eddie you mentioned two problems. The first problem is the fact that it's weekly typed and the second problem is the fact that it doesn't fail fast. So let's try and solve the first problem. This is where HLIS comes in. Shapeless and HLIS. So what is HLIS? It's a heterogeneous list. What's a heterogeneous list? It's a list that is typed over each one of its elements. So you might be wondering, well, what's the difference between HLIS and a tuple? So there's two key differences. The first one is that tuple offers a sort of list like API. HLIS does, sorry, yeah. HLIS offers a list like API and so I mean that's not like for me that's not the fundamental point for HLIS. You could imagine defining a better API on tuple. The fundamental advantage of HLIS is that it allows you to define generic function definitions. So if we were to define a sequence function over tuple we would need to write a sequence function for tuple one, a sequence function for tuple two, tuple three, tuple whatever and you get the point. Whereas with HLIS we can define a single definition of sequence over HLIS and that will support an arbitrary erity. Granted, the definition for sequence is gonna be a little bit more complicated. So this is just an example. I'm sure most of you here are familiar with HLIS assigning a very simple HLIS to X and just asserting the fact that this is actually typing the fact that the first element is a string, the second one's an int and the whole list is actually only two elements. Okay, so this is what we're sort of looking for. We're looking for a sequence that operates over an HLIS of future and is gonna produce for us a future of HLIS. Hey, this actually sort of already exists. That's cool. Shapeless Contrib provides a sequence over HLIS. Shapeless Contrib is a separate repo to Shapeless that sort of brings in Scala Z and a few other projects and gives you some really cool stuff. There's some really cool stuff you can do when you combine these projects together. So this HLIS is sort of defined over this container type in the same container. So what that means is this is not only a sequence that works over futures, it's also a sequence that works over any sort of container type. So you could sequence over option and like a HLIS of option and you'd get an option of HLIS. So that's sort of cool. So what is this container thing that I mentioned before? Yet another explanation of monads. Pretty much. So Scala Z, Scala Z as you probably know is a hierarchy of, provides a hierarchy of type classes sort of coming from Haskell. So defines monad. So monad is a container. Actually, that's not really all that sure. It's more specifically or more accurately, more accurately, it's a context for computation. It's actually just really anything that satisfies the laws of monads, but you know, the best intuition is probably just to say that it's a context for computation. I'm sorry, what? Yeah, exactly, yeah. What he said. So what are examples of monad? Option, future, either, try. Oh no, not try. Not try. Not try. So monad defines a combination function that's defined in a non-parallel friendly way. So this is the buying function that you'll find in Scala Z. It's pretty much the same thing as flat map. And you'll notice here that we need to have our A before we can access our F of B. So fundamentally, there's nothing we could do about, you know, checking if F of B has failed already or something like that. So it's at odds with what we're trying to do. So what we need is actually an applicative functor, as was already mentioned before in the previous talk. Well, briefly introduced. So an applicative functor is a monad, but the contrary is not necessarily true. So it's a little bit less restrictive. The combination function is defined in a much more parallel friendly way. So this is one of the functions that needs to be defined for you to instantiate an instance of applicative functor. And we see here that since we actually get the two Fs, we can do our fail fast. It's gonna enable us to do our fail fast. So applicative is what we want. In any case, the future's failing fast would probably not satisfy the monad laws. Although we really don't care about the monad laws that much, we just wanna fail fast. That's the business goal. So by the way, as everyone following us up until now, does anyone have any questions? We think that in showing what we did, there's actually some interesting insights. So we're just gonna move on as if, there's actually something relevant here. Okay, so just quickly present this sequence function that's defined in shapeless contrib. So here, defined sequence, our L here is an H list. You may sort of be wondering why L isn't just, why we even bother to do the L, why don't we just put H list? It's actually, because this is the only way to not lose the type information of the H list. And if you guys are familiar with H list, this is all totally makes sense. And then there's the implicit sequencer that is just a way of augmenting the type system in Scala. And then it produces sequence.out. Yeah, that's cool. It also comes with all of this, by the way. So yeah, I told you it was gonna be a little bit more complicated when you use H list. So no need to try and understand this. It's actually really blurry anyway. The only important thing here is that shapeless got the correct insight here to define this sequencing over something that can be considered an applicative. So that's really helpful for us, that's really cool. Okay, right. Yeah, so sequence is defined for things that are applicatives. And there already exists a monad definition for the standard future in Scala Z. And because all monads are applicatives, there therefore exists an applicative definition in Scala Z for the Scala future. And this is what it looks like. Well, kind of, this is a sort of greatly simplified version of what it looks like, but it preserves the key points that are important here. You'll see we have our bind method, which you can see is actually implemented with flat map. The signatures are basically the same. The properties are the same. And yes, it is not fail fast friendly. We must wait for F8 to complete before we can do anything with F to obtain that next future. You'll also notice there is a point definition here. And that is the second thing required in order to define a monad, at least within the context of Scala Z. And point is just basically, give me the minimal context for this monad for any value A. So I say point of two and I get future of two. Pretty simple. Okay, so remember though, we were saying that what's important for the H or the sequencer is an applicative. So it's not gonna be using bind, it's gonna be using app, which is defined on applicative. The thing is based on the inheritance hierarchy for monad in Scala Z, it will automatically provide an instance of app that is implemented with bind, which as we know, is not defined in a parallel friendly fashion. So this is how they define app with bind, which works, but we don't get to take advantage of the fact that app is potentially fail fast friendly. Yes, is a question over there? Okay, future.zip is also not fail fast. Yeah, you must wait for the first future to complete before looking at the second future. They can be executed in parallel, but you have to wait for the first guy to finish, even if the other guy's already failed. Yes, question. Yeah, and that was supposed to be an exciting transition into the slide where I show you that, but I forgot about this really sweet animation that we had there with this. So it does not fail fast in sequence. Okay, you want to see it again? Boom! Wow! All right, so with that excellent segue, this is our applicative definition for a fail fast future. Again, we had to define point and we have to define this app function here. And so we take our future of A and a future of function from A to B and we use promises to do this. We instantiate a promise here whose future is the value that we're going to return for each of the futures that we take in. If either of them fail, we register this callback immediately fail the future that we're going to be returning. In the case that they both succeed, we can use zip here because, well, if they both succeed, we have to wait for both of them, that makes sense. And on success of both of those, we're going to complete the promise successfully with using this function to transform the value of A. And if you're wondering how this can be used to build lists of things, it's because the F passed in here is a partially applied cons operator that contains the rest of the list. And A is just your general value. So yeah, this is it, right? I said it was pretty simple. All we did was the app function here. It's like six lines or something like that. And it's really straightforward code. All we had to do was define this and make it available and boom, now the shapeless contra sequence function will automatically fail fast with all the same properties as it had before. It's just now we get fail fastiness and are typed up with, so, bray. So now that we've created it, how do we use it? Like I said, you must make it available. So it has to be the highest priority, applicative instance that the sequence function will look for. And one way to do that is just to make it available in the same scope as your calling sequence. So I'll instantiate a new fail fast future and then sequence over some futures here. And this example is not very exciting because nobody fails, but you can see we have typed output here, right? I can call substring on high without providing the type information. It knows that two is an integer and I can multiply it. Here we can see an example where something fails. I guess you're gonna have to trust me that this is what happens, but you can go try it for yourself. We make our fail fast future available here. We have a future that takes five seconds to complete successfully. We have a future that throws right now. And then we sequence over these and this will actually fail fast. We'll get a future of this exception right when it throws. One small thing, if you are gonna try this for yourself, it's useful to know that sequence is actually going to look at the last element first because it's doing this recursively. So make sure you put your thing that is gonna fail as the first parameter instead of the second if you want to see it fail fast versus not fail fast when you use the fail fast instance versus the standardly defined monad. Yeah. I think that's it, I think you're up. Oh yeah, one small caveat is type inference is sometimes tricky. Like I said, you need to make this instance of the applicative for future available with the highest priority for sequence when you call sequence. Sometimes things don't work quite right the way you expect, sometimes priority is tricky. So if you're seeing weird errors or if you're seeing implicit val not found or something like that, things that we found that work well are explicitly defining types on input values perhaps or making sure you're importing all the implicit depths you need. Okay, so we define an instance of future that fails fast. So in the course of the project that actually prompted us to solve this problem, we actually wanted to create our own abstraction that was very similar to future we called it cumulative task inspired sort of from the pattern in Scala Z where you have future that doesn't handle air handling and then you have task that does that handles air handling in the same fashion as Scala futures. What we needed is that, you do a few database scores or whatever sometimes you get some warnings back whether you succeed or fail when you have a series of computation if you had some warnings in the first few sort of computations we wanna get those warnings at the end, at the very end. So we use the same pattern where cause a normal future would just fail and then you sort of lose everything. So we just basically wrapped future with a disjunction of throwable and A and then a list of E which could be any accumulation. We have warnings. Another good example I like because our different plans have different prices and if it's a pro customer or just a client from our website we don't necessarily query the same data providers and whatnot. So another example of an accumulation would be price. So you could accumulate the price of every one of your calls that create the whole cumulative task that is the whole like sort of plan. And this actually presented some challenges. So we need to solve the same problem. We want to sequence over a cumulative task. And the question is, can we make this a monad or actually an applicative that should have changed applicative. But anyway, it's the same problem. So let's look at the definition of a monad here and we can see here that F here is sort of like a higher kind of type. And so you can think of it basically as a function that takes a specific type and returns a specific type. So an example of that would be like list. List is not really a specific type. It's a type you give it like an ant, then becomes a list of ant that's a specific type. And so monad expects a function that takes one single specific type and it'll produce a specific type. But our function takes two types. So it doesn't just naturally fit in to this monad thing here. So the question is, how can we sort of partially apply our class and to just fit into the monad basically. And so this is how you do that in Scala. So we're defining here sort of an inline N type that takes an A and then we're saying that's actually equal to a cumulative task and we're setting string and then projecting the N and that's called a type projection and that's how you would partially apply something. In Haskell this is much simpler. You can partially apply higher type constructors in the same way you'd partially apply functions like Korean. So this is what our new monad instance looks like for cumulative task. You'll notice here we mentioned that point is the minimum context for a computation. So we see here that just for people that are not familiar with super with monads that list empty is a natural empty context. So there is a better way to do this. The kind projector plugin written by Eric right here. We didn't know he'd be there. So yeah, it allows us to, you know, this is what we had and add compiler plugin and yay, this is much better, much easier to understand. You can argue this is even possibly better than Haskell because you can sort of partially apply. In Haskell, if you wanted to partially apply in a weird order, you'd have to like flip. Yeah, flip and play around with that. Where here you can just like exactly put it where you want. So that, whatever, okay. You should never say that something is better than that. Yeah, yeah, my error, I'm sorry. Okay, okay. Anyway, we thought it was cool. So this is our new instantiation of monad for cumulative task. Although now we do it over replicative because then we don't, we respect laws, possibly. I don't know. I'm not sure I totally understood the question. T, sorry, what? A monad transformer either T. Okay, no, no, but. I think we can stare, so we'll talk later. Yeah, well, so like we would have had to use either instead of our, well, our distraction, right? Okay, well, that's interesting. Yeah, we'll talk, we'll talk. That's cool. Anyway, we ran into a bit of a, so, okay. Basically we have our instance, right? Cool, now we should be able to sequence over a cumulative task. Actually, no, it didn't work. Why didn't it work? So at first we figured, okay, well, whatever. Let's just write our own sequencer, you know, the big piece of code you saw before. And that worked, actually. But that was like a lot of code and we're like, hmm, is there a better way? And so there's actually another piece of code that comes with the sequencer function that I didn't show earlier just because I didn't want you guys to run away. But yeah, this is, it's a pretty big file just to define one function. But there's, what you need to understand here is there's this apply to thing that really, when we're defining our monad over future, this is the function that's getting called in. It's not super clear, but you can see here F over, you know, this is the natural definition to take. Because this F is gonna be our cumulative task or our future. So if it's future, it's gonna go into here and it's just gonna sort of work. But if it's a class that takes two type parameters, it's gonna go into apply to A here. And you can see here that basically this is doing the same thing. This is like a wrapper trying to like abstract over these things. And here it's gonna define an instance of apply, which, and this is the type projection we saw earlier. And I'm gonna yeah, zoom in on that. So we see here, this is the line I was showing, so apply and then the type lambda. And so given this, does anyone have any idea why this was initially not working for us? Yeah, there's a bit of a hint in the title of the slide. So the thing here is if you notice the lambda projection being done here, the X is being filled in in the second position. And our original definition that showed up here, I should have told you is we define AE. Because we, you know, we're, I would consider myself fairly functional programmer now, but I've still come a little bit from an object oriented background. I'm the natural reflex to put the most important type parameter first. So we figure let's put A first. And because of this, they work. And so we figured we just need to change the order. And then we could get rid of a whole lot of code because now the sequence has worked. So it turns out that you wanna place your more important type arguments last. And that's really a convention that came from Haskell because in Haskell, you know, occurring is much more natural when you're more important type parameter or even just parameter to a normal function comes last. It's just more natural. You don't have to call the, what is it again? The function that flip, flip. Yeah, the function that flips. Anyway, so yeah, that was like a bit of a, we would have saved a lot of time. And that was, so that's a bit of a takeaway. You probably wanna respect the convention when you're dealing with ScalaZ and Shapeless because they're gonna assume you do. Anyway, so yeah, we just wanna thank a few people, Robert Noble, our manager, who let us do a lot of pair programming which is not super common at white pages and allowed us to solve this problem because we don't think we would have necessarily done it on our own. Also, Eric for the plugin. Thank you. Travis Brown answered a lot of our questions on Stack Overflow that sort of allowed it to us, unblocked us on some issues. And finally, Myles for Shapeless and Shapeless Contrib which is like really, really cool to work with. And finally, we're hiring. So if any of you found this really, really, really simple, we would like to hire you. But honestly, if this was just interesting and maybe not that simple, then we're still interested. So thank you.