Composing Interactive Applications With Zelkova

0 0

my name is James and I'm gonna be talking about a library I've been writing called zelkova this is an 800 year old zelkova tree it's living in a shrine in Japan the zelkova trees are in the same family as elm trees zelkova the library is basically a reimplementation of parts of a language called elm into closure and closure script Elm was made by this guy Evan kabuki he he did his senior thesis a few years ago on this language that he made it targets the browser and it's specifically for building graphical user interfaces with functional reactive programming Elm is really fun Bodil Stoica at Strange Loop last year described it as if Haskell were designed by a usability expert so it's pretty nice so there's lots of different kinds of FRP out there functional reactive programming if you're curious about how Elms model of it relates with other kinds then I highly recommend Evans talk from strangers of last year controlling time and space he really goes into the trade-offs in a good way but zelkova is is based on Elms version of FRP and so it's really all about signals what is a signal in this case it's a value that changes over time in the closure community though we have this really strong separation between values and identity we rich has made this very clear to us all and we we have this notion that a value is just an inert immutable snapshot in time and that an identity has different states of different values over time so what our signals are they values or identities well I like to think of signals as values which represent ident in in all the ways that are really useful when you're working with signals in Elmore zelkova they are immutable you can pass them around and not worry about anyone else messing with them and screwing up your your code and but they do represent sequence of values over time and this is this is kind of what makes them cool so I'm going to get into a demo okay so I'm gonna show you a little flyer game not not quite a game at the beginning of one and here's the beginning of it I'm using quill to do the rendering quill has its own ways of doing managing events coming in but we're gonna use zelkova for that part just whoops just to just to show how it works okay so just to show you the code that's already here I won't get into the quill rendering code too much but basically it's just drawing this triangle on the screen and it it has state that quill manages in an atom and I have this namespace of some vector math functions to use things like converting into angles in radians or messing with the magnitude so we're gonna use that for some geometry but most of the code I'm gonna put into this namespace my signal name space and I've already included some required some of Zeljko BOS namespaces for this one we're just going to be using the mouse and the core signal namespace which are typically aliases xever zelkova okay so let's get started just to show you putting something on screen I'm just gonna get the the ship to follow the mouse for now just to just to be just to be quick about things so here we're gonna make a steering function eventually it's gonna like steer the ship but right now is just gonna the ship is just gonna follow the mouse it's gonna take a state of the shape that we have over here its flyer with position angle velocity and mouse position which is going to be a vector of X and y and so we're just gonna so since state fire okay so we're just updating the flyer position with the most position we're gonna write this app signal function which is going to take in an initial state and return a zelkova signal for this game we are gonna want to manage the state in zelkova and be able to change the state based on like update it in a using a reducer function basically so I'll show you how this works is zelkova we have this reductions function if you used to Elm this is like fold P except its arguments are reversed so it plays nice with closure so right now we're just gonna take in steering wheel just steering the initial state and most position and most position is itself a signal and it's I don't need to call it as a function to get the signal it's it's just already a signal and the namespace provides it and because it's just an immutable value it's fine it can be assured by different signal graphs that might have a bunch of them going on but for now we're just doing one and basically what this is gonna do is gonna take the initial state reduce basically the the reductions signal becomes a signal of successive applications of this steering function on the state with incoming those positions so here to wire it into our quill sketch I'm going to do take the initial States call that app signal function to get it Zeljko of a signal from it and then I'm going to use this pipe to Adam function which is is going to take all the values from the signal and just reset the atom that you give it so here I'm using the state atom that quill the quill has baked in okay so let's see if this does something yeah okay so here we have it following the mouse so it's a start okay I'll go back to this signal namespace and now I'm gonna make it so that it's actually gonna steer instead so the first thing I'll do is get okay I'll get the fire position because we do need that to figure out the angles I don't get like a difference vector so I'll use my vector math namespace and subtract the mouse position from the flower position to get an offset and I'll calculate the angle with my vector 2 radians function and then I will change this associate to update the angle instead and then I'll update the angle like that okay so already I can see that this is too boring for Tuesday morning so I'm going to get an image that I have for the for the flyer just so that it's not a boring triangle and then let's see down here I'm gonna get that out of the state of the flyer and then get rid of the triangle oops okay so there okay so here's here's our flyer now it's a little bit better but it's it's it's a little big and it's it's not pointed in the right direction really so I'm just going to let's see I'm gonna rotate a little bit more good and and I'm gonna scale it down a little bit we don't want him that huge good okay so okay now we want to move rich around so we're gonna add a new function that's gonna update the state similar to steering but it's gonna be thrust this time so it's gonna take a state and a boolean value of just whether or not we're going or not so we're gonna do something we're gonna do some calculations if the toggle is on so we're gonna get the angle from the flyer and then we're going to update the state's update the position of the flyer in the state and the way we're gonna do that is we're gonna take the angle we're gonna convert it into a vector like so it'll just be like coordinates in whatever direction you're in and then we're gonna add that to the current position so we're just gonna wherever your mouse is pointing the thrust is just gonna go in that direction by like one unit so to wire this in we're gonna do basically at this point we have mouse position and we want to be able to control thrust somehow so I'm gonna I'm gonna bring in the keyboard I lied before we actually do need another namespace oops okay so we have the keyboard now and we need to bring those together so in zelkova you can you can use the map function of zelkova it's not the regular map function and it has the same basic function signature as as in closure core so you can pass it multiple signals to get values from in the same way that enclosure cores map you can get you can pass multiple collections to get values from and in this case our mapping function is going to be vector so we're gonna take the keyboard space signal which gives you a boolean of whether or not the space bar is down and and the most position and then map them into vectors so anytime either one of those signals UPS updates this map signal is gonna update as a vector of the two current ones the two the current space being depressed and the current Mouse position so we're gonna call that inputs and then we're going to put it in here and we need to change this reducing function pasture reductions to B instead of steering it needs to be something that knows about these vectors so it's going to take the state and a vector of space bar and as position and then it's going to thread this state through the thrust function passing it the second argument of the toggle and also thread it through the during function okay I think this should work now so here I forgot the else clause of the if so it just got rid of the state entirely anyway so I need to say don't do anything to the state if we don't have this base for are depressed okay so so now you can move around but it's there's something weird well okay the problem is that every time I hit the spacebar no matter how long I hold it down it's just like one pixel so but if I move the mouse then it does that so what's going on here is that the the map function in zelkova is it's updating whenever either of the inputs are updating and the spacebar input only updates when it changes so but the most position changes whenever the mouse moves so he can go as long as the mouse is moving or he can go like that but not otherwise so the way we're gonna fix that is we're going to use a clock so yet another may suppose I forgot about so we need time Elmen zelkova are have it it's a pretty good model for working with time it so you can you can do there's a few different things in this namespace but the one we're gonna use here is FPS so FPS is gonna take the number you give it as a number of frames per second it's gonna give you a signal that updates that many times per second with the delta in milliseconds from the last time that updated so we're gonna use that we're gonna just slaughter it in two at the beginning of that vector we're gonna let our reductions function know about it and then that should be good enough to at least let me hold this base bar down okay but we want to we want to be a little bit more realistic with our game or at least it needs some it needs some physics it's not going to be the most real physics in the world but it'll be some kind of physics so we're gonna instead of talking just about thrust as some fake you know a constant thing we're gonna instead talk about acceleration so we're gonna rename it to acceleration and we're going to have let's see so instead of updating the position we're gonna update the velocity and right now that wouldn't do anything but we're gonna add another little bit of physics called inertia it's going to take a state and a time Delta this time so we're gonna calculate a translation to figure out how like how we should move the ship so we're gonna multiply the velocity by the time Delta to figure out how much time has passed so how much we should move the ship okay so now that we have inertia so and and this is just yeah using vector addition to to translate the current position so now that we have acceleration we're gonna or and inertia two now we're going to put the inertia in here threading it into our little pipeline give it the time Delta and what oh that's really quick okay when you do do some tweaking here okay so clearly we need to multiply some more so here it is slightly more manageable but okay so we've the this time you know every frame we translate a little bit less but the the acceleration is still pretty wild so so I'm going to put a limit on it you know limit the magnitude of the vector that we end up with for the the velocity so we're gonna limit the velocity to one so we're gonna just think in like one is the max and you do okay okay so that's still that's better but it still feels a bit weird so the problem is that right now it's it's limiting to one but but actually it's starting at one two I think because we start with a normalized vector so we need to just take the the off the the vector that we're adding to the velocity the the thrust that we end up with we're going to make it a magnitude of point two always so every frame that you're accelerating it's only gonna increase the velocity by a magnitude of 0.2 and then that's that's a little bit better but it's there's something weird going on here you can see how every time I every time I move my mouse and goes faster okay so what's going on here is that we still have this map signal updating every time any of the inputs updates which means that and the the value at any given point in time includes you know the the fresh value of one of these signals which just updated but also the previous values of the others and we thread through all of these different things inertia acceleration and steering every time so what's happening is that we are calculating inertia not just every clock tick but every time the most moves as well so what we need to do is take this signal and use another function in zelkova called sample on and we're gonna sample it on the time delta the signal so we have this signal of vectors with all our inputs and instead of just getting all of them were only gonna sample the current value of that every time the clock ticks so everything is mediated by the ticker now and now no matter no matter how fast I move my mouse he's he's still he's not gonna go any faster yeah okay so so that's the end of this demo okay okay so those that was good fun so this is basically the the graph of signals that we made so we can see that this is like pretty much everything good in the world it's a directed acyclic graph and you might be wondering now why not just use core gazing so quarry sink is you know we you can manage incoming events with quarry sink you can merge channels together there's a bunch of good functions in quarry sink to help you with all that well zelkova is using it it's implemented in quarry sink so what happens is when you use the pipe to atom function for example it it spins up like a live graph of basically core async molt's that are so multi layer you emit values to multiple other channels so Zhukova currently the implementation is that it uses it quarry sink melts behind the scenes to wire everything up this red splotchiness is basically just event propagation things are things are moving and in fact you can take the same zelkova signal and produce multiple live running graphs from it because the the signals are just immutable values so they're like recipes for for spinning up live graphs here's another way of getting at an actual live graph object from zelkova you can use the spawn function and then live graphs conform to the quarry sink mult interface or protocol so so you can use the quarry sink tap function to tap a live graph on to on to multiple channels if you want okay so why not just use quarry sink though I mean it's built on curry sink but what why do we need this so the reason why it's good is that signals are values transformed by pure functions they can be shared freely with different parts of your code and the signal graph is synchronous and deterministic I don't get too much into that aspect in this talk Evon does in controlling time and space more but basically you can I mean we use quarry sink to get this synchronicity but if you do it on your own with a graph that has diamonds in it and everything then it's not obvious necessarily how to do it and it's zelkova basically does all that work for you so we can push mutation to the edges and get a reasonable language for talking about time this is something that I think is one of the big strengths of Elm okay so we've played around with with fun little game but now it's time to get down to business okay so a lot of people look at Elm and they see a lot of games in the demos and they think that it's kind of toy language sometimes building big applications in Elm is definitely an area of active research but evan has been and others have been putting a lot of time and effort into figuring out and exploring this space there's a great github repo that evan has on how to structure larger locations in elm and it's it's really interesting what they're coming up with but for the demo that I'm going to do right now I'm just going to instead of instead of doing like a big application all with zelkova I'm gonna instead have an ohm ohm component and show you how you can use zelkova to to have a single graph operating on an ohm component so so you can just use like a little sprinkle of zelkova when you want some nice event coordination stuff okay okay so I have a project here it's got some code in it already basically what we're gonna do is have a little Wikipedia search field that we're gonna show some autocomplete suggestions underneath it's not gonna be very full-featured it's just gonna show you how we can get some benefit from zelkova with ohm and other libraries like it like reagent and quiescent okay so right now this is just a this is just a template there's nothing going on there so that's the HTML for the template and we have a couple functions and I'm going to be using so here is how we can convert a search query into a JSONP request for Wikipedia here's a utility function to take a channel and merge its values into an ohm cursor so here you can see we're using the closure core merge function to do that and here is just a convenience function for turning a channel into a an input Dom handler that will put the current value of the input fields onto the channel okay and so there is oh yeah okay so there's one other namespace that we have with some JSONP stuff so so talk to a Wikipedia we're going to be we're going to be using okay I do have a back-up plan is the Wi-Fi on yeah okay that's the problem okay oh great so okay so I'm not going to get into this code too much but basically this function is a JSONP pipeline we're calling it so it takes two channels chorionic channels from channel and a to channel so from is gonna be a channel of requests which are gonna be maps that have URL and params keys in them and then two channels just some other channel that you want to eventually get responses on and so we use the Google closure libraries to do some JSONP we convert back and forth between closure data structures and JavaScript and one other important thing that we do is we attach the original request as metadata onto the response then here we have a function called fetch responses and with this one we're gonna take in a zelkova signal of requests and return signal of responses so we're using the splice function here and the gist of that basically is that we get to tap into the signal graph on the level of Cori sync channels and we end up with a signal that is ends up being not synchronized with the rest of the signals but instead it's it's like an asynchronous signal so it's as if it's another it's as if it's another source of input events like most position so it the responses could come at any time okay okay so let's get started so we want to wire this up so that when we type stuff in we're gonna get zelkova to do stuff so the first thing I'll do is start with some states on this own component so the first thing we're gonna do is make a zelkova right port so a right port is kind of signal it this is a signal constructor in zelkova but the signal that you get back you can also treat it like a core async channel this is kind of a gray area with like you know is this really a immutable value anymore because you can push values onto it but the important thing is that this signal can be shared between multiple running graphs so you could have multiple graphs that share structure and you can be putting values onto one of the right ports and every graph gets it they don't compete for the values okay so we're gonna initialize this right port with the text in the in the props so this is like an ohm cursor that we get and it has a text field that we're gonna introduce the right port with and then the state that we're gonna return here as the initial state of the component is it's gonna have an input handler that the text field is going to use so we're going to initialize we're gonna use that function that I mentioned before channel input handler we're gonna pass it the right port which can act as a channel and we get back handler that the input can use and then the other thing is we're gonna just have a key called signal which is gonna be like the main signal of the component and that signal is going to be we're gonna say we're going to take in the okay we're gonna take in the text that we get from the text input and return a map of texts to text okay it'll still become a little bit clearer in a second we're gonna implement the will mount protocol of ohm so that so this is where we're gonna wire up where are things up so so far we just have inert signals but we're gonna now take these signals and hook them into the component so we're gonna get the the signal from the state we're gonna call a another zelkova function this is like another convenience function like pipe to Adam but in this case it just gives you a channel and increasing channel back so we're gonna turn the signal into a channel that we can get values from that way and then we're going to merge channel values into the cursor okay not quite done this iteration yet so the other things we need to do a couple other things so one is we need to get the text from the cursor and bind that to the input fields value and then we want to attach this text input handler to to the input and just to show that things are actually doing something instead of this text here we'll use text from the cursor okay so it's doing something so so that's how we can wire up the the the states and because I only have a couple minutes left I'm going to just jump to the end and show you how how this works so okay so basically the this is the final results but basically the we have we end up using the debounce function that zelkova provides to debounce the input we fetch responses we drop the stale ones by checking the metadata of the response against the last request that was sent out and the end results is that we can actually search Wikipedia okay I also have a reagent version of this which I don't have time to show but I will have it online so you can see that - all right so zelkova is on github it's on closures I just pushed 0 for 0 last night it's got the functions that I was using today there's a to do MVC demo there's a Mario demo which is part of the Elm version and there's this drag-and-drop demo that shows you dragging it up and shows you the as a output of the state there of the final signal and also I wanted to give a shout out to Dan literal Porter who is also giving a talk at closure West and he actually has the as far as I know the first production deploy of zelkova and it's this magic card deck building thing and it uses it for the Dragon drop okay so things to do for zelkova I want to make it so that the states of the signal graph the running graph is exposed this will make it better for using it with food will and and just better in other ways I have been thinking of ways of compiling a signal to a transducer of events to output values that's gonna also play in with how I externalise the states Elm has a fantastic time-traveling debugger which it's once I get the state externalized and other stuff then implementing something like that is gonna be easier and also to go further with signals as data so that we can you know manipulate the whole graph and do graph transformations on it and other ideas special thanks to Richie key David Nolan Alex Miller everyone else who put so much time and effort into closure Evans Palicki for elm and very special thanks to my wife Julia Shea who has been incredibly supportive and encouraging through all of us thanks very much