scale.bythebay.io: Vincent Marquez, Index your State for Safer Functional APIs
Recording: scale.bythebay.io: Vincent Marquez, Index your State for Safer Functional APIs
you all right everyone this is in Dexter State for safer functional api's and we'll start with who am I I'm Vincent Marquez that's my twitter handle if you guys want to argue with me about functional programming I encourage it that's my github so I'm currently working at Verizon labs I must call as a contributor committer hazel cast contributor and maybe an S coda contributor where's Michael Bill Quest merged my poll requests all right so I build a lot of api's for my co-workers and this is gonna be kind of a basic API that I might have done a couple years ago and we're gonna talk about how we could improve it so here we have this idea of a connection and we can do some things to the connection right we can set the destination we can initialize some credentials we connect we can send data but well I'm not going to say this API is bad I'm gonna think I'm gonna say that I think we can improve it right the types don't give us a lot of information they don't tell us how long things take if something's happening asynchronously they don't tell us if the something is mutating and and we are mutating things or at least changing things right the state of the connection is changing we start with the unconnected you know state of the connection and then we're gonna go to a connection a connect a connected state we're gonna be in a state that we haven't sent any data and then we're gonna have a state of the connection where we either already sent data or it represents a computation that will send data so we have this API that's kind of lying to us so let's try again our this is a little better this kind of is the immutable style connection right we set the destination and we hit a new connection that represents the connection with now a destination set we initialize some credentials and we know that this might take a little while there may be some a synchronous computation happening to give us this new connection state same with Kinect and same with Sun data it's kind of same idea we're we're capturing the the fact that a new state of our connection happens and in these two this case that's happening inside a context of the future okay but using the API is a little weird if we do it this way so here we're gonna use it in a for comprehension you can initialize some credentials we'll get the new state so then we have to make sure we connect on the new initialize credential state right we can't call conduct connect anymore because that's not gonna work then we have to make sure that we now send data on the initialized connected state so we kinda have to manage this like updated state as we go through our using our API which which is a little messy in my opinion it's it's error-prone right I could forget to accidentally type the new updated state so it'd be really nice if there was a way to thread through I state implicitly and I'm guessing some of you know what the answer is state monad so I'm gonna go through the basics of the state monad to start with and then I'm gonna talk about some of the limitations of the state monad and ways we can even improve the state motor so what is the state monad you learn your Haskell for great good that says that stateful computation is a function that takes some state and returns a value along with some new state and the function will have the following type takes a which is an initial state and then returns a tuple of the new state and the result of the computation so I'm gonna do a really simple example with the counter so our count state just returns a state monad that wraps our function which is an input of int and then it does something to the int in this case it's just incrementing and then it's returning a value of the computation which is the string representation so now when I use this for comprehension you know what I'm calling count state multiple times I'm implicitly good this plus one on the state side of the computation and then I'm returning a different value in this case a stream which doesn't really do much because uppercase on a integer representation doesn't give us anything but hopefully you see the point of of we now have a way to implicitly get some state changes for free let's talk about like the Platonic ideal of how the state monad works I'm just gonna wrap computation that looks very similar to what we talked about and they'll learn you a Haskell for great good and that's going to be RF takes an input to state and returns a tuple of the new state and a computation kind of where the magic happens is all in the flat map now if this is the first time you've ever seen state monad you're gonna want to work through this I think multiple times or read the read book functional programming in Scala but it is a little weird the first time you see it so if you've seen it before then maybe this will help make it click again or be a refresher the flat map looks just like flat maps from anything else just like the flat map from option just like the flat map from future list tasks your own custom flat map hopefully follows this but the idea is you're taking you're passing in a function that takes the the type of whatever we have in this case of B and returns a new simple state of the same state type and a new type for the result of the computation and then it flattens that out into one new simple state of the initial state type and the result of the computation and how does that work well it just you kind of follow the functions you create a new simple state that's what its returning and inside that you're chaining your old function through passing the state from that into you're the result of that into your flat mapped function getting the state out of that and passing it in do that's that flat mapped function so that's what that's this is where we're actually threading through all the state right there so that's simple state so let's say you guys want to start using the state monad you saw my counter example and you're like alright I've got a good idea to use this in production and you start deciding that maybe I won't write my own statement and I'll use scholars that state monad and you're using it happily and one day you get type error and you see it says found scholars that state int string expands to index state tid int int string and you're like what the heck that doesn't make any sense well like a good software engineer you guys want understand so you decide to dig into the source right so the first thing you're gonna find is type state equal state t ID si all right well let's follow that let's figure out what state Tia's actually type state T equals indexed say TFS si okay the rabbit hole goes deep right what's going on okay we're gonna look at the source okay we've got an F a contravariant input another state and all this other stuff okay like what what's going on right what monsters wrote this I don't know but we can figure it out but we're gonna go step by step to figure out what's going on and why it's actually really useful so now we're gonna do state T and the T stands for transformer so actually we're doing a mini monad transformer tutorial before I get to what I want to talk about and this is gonna look really familiar to my simple state the difference is instead of returning a tuple of a and B I'm returning an F that wraps the tuple of a and B and what's the F it could be anything it could be your option it could be your either your disjunction your future your time your custom monad doesn't matter right okay how does it work again magic kind of happens in flatmap and we're gonna see that it looks pretty much the same in this case it's not a reply month but we have we're saying we need to have a monad meaning we need to be able to flat map the eff and otherwise it's exactly the same the the same idea of taking this this thing that this cut of this FC the hand or the flat map function that handles the result of the computation and gives us a new state T and we're merging these together by threading through the states the only difference is we have to call flat map on the F rather than just running it so it gives us the same idea of of state except now we have this context around our state computation but we'd have to worry about it right here's an example instead of instead of just incrementing one and returning the string I'm gonna do this a synchronously on a thread pool maybe not the best idea but it illustrates my point well count state count state so now we're just going to yield the new what we're gonna get is we're gonna get a stateful computation that spits out a future with the updated state so it's pretty much the same it just handles the fact that instead of a state we have a context around our state and our result of the computation all right now we get to the interesting thing in my opinion I think it's all interesting but here's the more interesting part because nobody uses index state and they should so what's this whole index state thing about okay let's talk about a more complicated API this API will imagine talks to some external resource and it's a little transactional right we're going to be able to view things and update things but we have to lock them first and then we have to commit them after we we actually update them and this kind of happens happens we don't really know how it happens but we want to have an API that just describes that this happens and the in the the initial state that we want to deal with is an immutable cache right this way we can update the cache immutably and state fully and kind of thread this cache through so as we view things or whatnot we don't have to re query and we can save some some time so what do we have we have we have view we have lock right this one's gonna give us unit because it's kind of a side-effect D computation we have update it's gonna give us the new item with the updated value and we have commit and it's gonna actually like persist it right or let other people see that we updated it okay so what's wrong with this API I mean it's not bad but what can we do with it I'll show you kind of this is just an example of how we can how the API would look I mean this is less important but the idea is in the state we're implicitly updating and checking the the cache it kind of shows like a more realistic example of why you might use the state monad we can immutably update and thread through this cached item list so so here's here's a usage of our IP API view and item and then we lock an item and we update an item not bad so like I said what's the problem there's a problem in my opinion we're threading through the state but we're not modeling it so this is the issue this compiles we can view an item we can update an item and then we can lock an item but that shouldn't compile right that's not what we designed our API to do and we want to provide safe api's for our users so is there a way to make sure that this doesn't compile well that's kind of why we have the indexed statement it's going to help us write an API that enforces how the state changes happen so what's index state here's my simple index state I might have named that wrong but you know simple ish we've got again a computation that takes an initial state and returns a context around a tuple the difference is there's two types here there's an A and a B so that's the starting type and the ending type the ending type of the state in addition to the computation that's that's the magic that's all the index state is instead of having a state that you update that's the same type you have a state that you update that's a different type and you can see how this actually works in the flat map it's this same flat map but the types just have to line up instead of outputting a new index state from a computation that takes our output of C with the type of a we have to get a type that takes a B and returns a D right so our index state result of the flat map is now an ad rather than it a B right so we're we're just making sure all the types line up when we call flat maps otherwise it's the exact same state monad magic of peeling out the tuple passing in the rich and Michelle oops yeah there we go passing in the initial value and then threading that through to the next computation so let's model more States this is the way we can kind of enforce our API a little more clearly instead of just a cache date we're gonna have this idea of a lock state which is the same ID the same cache data and we have an update state and let's see what we can do with this so our new API our new API view is a cache date to a cache date right it doesn't change anything we don't we don't care how many times we view and what we do after that but a cache date starts with a cache to air a lock item tate starts with a cache date and returns a lock to item state update item requires a locked state and gives us an update state and again commit takes an updated state and gives us back a cache date so now what happens when we try to use our API view item and then calling update item and calling lock item gives us compile time safety to make sure we're forcing our users of the API to call our API in the right state full way so this is the air so this is kind of why it works now I don't have time to go into the fact that index state is contravariant but that's why you're able to use subclasses to to model the state and and still yeah we can talk about the contravariance later so so this allows us to really enforce the way we want the order of our users to call their api so what else can we do I don't know this is just something we've used a little bit in one of our API is that Verizon I'd like to see more ideas on how we can improve this I think it's a cool idea I think it's underused but I'm curious to talk to people about it if they my ideas and suggestions and criticisms so yeah that's that's index your state Thanks [Applause] I will put them on column by scale by the bay and I'll I have a older version of this talk on my Twitter too so if you go to my Twitter it should be opinion yeah so if you go to skull zette and you look up the state tea you know you'll have the index state and there should be examples in there somewhere too yeah but that yeah so I I try to make type aliases whenever I'm using something like this so people aren't scared away but and and I and for the first time in my life I actually wrote like a lot of documentation on this API like usually in Scala it's pretty straightforward of like what it returns a future of a tuple right this is the one time where I'm like alright let me write like a mini book to make sure people don't freak out so there's just some training involved when your coworkers are like I don't think I can handle this you have to spend the time to say hey look it's it sounds crazy but we can we can do this so that's what the trade-off in my opinion yeah the API was kind of that it's similar to what an API internal Verizon API we used yeah but that but the index state stuff is all from skal is that so that's what we're using we use a lot of skulls I don't Verizon [Music] I don't know I haven't thought about that's interesting alright thanks guys [Applause]