
Programming Games with Shiny || Roll the Dice: with Quosures! || RStudio
00:00 Introduction 03:44 The pain of copy + paste 07:28 Going on a helper function adventure! 18:09 Ready for rlang 28:17 !! + enquo() 37:57 Benefits of the rlang approach 38:46 Embracing the embrace operator 41:33 Visualizing what's happening using reactlog You've most likely used Shiny to build a web app that displays data, but you can also use Shiny to build games! In this video series, Jesse and Barret pair program simply games in Shiny as a way to uncover and explore new features. Read up on the embrace operator here: https://rlang.r-lib.org/reference/embrace-operator.html Learn more about Shiny here: https://shiny.rstudio.com/ Got questions? The RStudio Community site is a great place to get assistance: https://community.rstudio.com/ Content: Barret Schloerke (@schloerke) and Jesse Mostipak (@kierisi) Animation, motion design, and editing: Jesse Mostipak (@kierisi) Theme song: Hakodate Line by Blue Dot Sessions (https://app.sessions.blue/browse/track/111291")
image: thumbnail.jpg
Transcript#
This transcript was generated automatically and may contain errors.
What are we going to do today, Barrett? We are going to get all up in the Quosure business. Quosures! Quosures, and we're going to embrace them, and it's going to be great.
All right, well, let's do it. What are we trying to accomplish? So you have, I guess, four contacts. You sent me some code ahead of time, and we're like, here's where we're going today, and I was like, cool, and then I ate lunch.
So let's take a look at the server function just so we can see this, and then I'm going to, we're going to kind of double what's here. So we're going to have two buttons for rolling the dice, and we're going to try to hide a lot of this logic so that we can kind of say, like, from 23 to 38, that's like, roll a dice, and that's it. Like, we can just listen to things, and we can try to hide this away, and I think it's really useful when you're trying to make your server function not over a thousand lines of code, and it can actually be maintainable and reusable, but still use expressions that Shiny is, like, wanting you to use. And I think we'll kind of see it happen and how it's kind of not easy to do unless we use some closures with rlang.
So let's try to see on, like, how, like, what would we need to do if we were going to have two dice buttons, and roll the dice buttons, and two button outputs. Let's kind of change the code from there. Right, so you're saying we want roll, roll another dice. Yes. And then you want output for dice one, and then an output for dice two. Sounds great.
Okay, so I would go to the UI. That's usually my, so I would do something like action button, and then typing out all my variable names. Let's make them consistent. I hope that all of the comments are about how semantically, like, the grammar of die versus dice. Yes, truly what I hope for.
The pain of copy + paste
So, if we were to do this again, I think the, like, one of the easiest ways, like, let's just walk through the 30 seconds of pain that this will be. So, let's copy 33 to 48, because that's all the logic that we need. If you want, we can put it, yeah, right after 48. That works.
All right. So, let's add some labels here, just so I remember what we're doing. So, this is original code one dice, and that's definitely not right, but that's okay.
All right. And we would also need, let's duplicate 70 to 73, and have that print output dice two.
Okay. All right. I broke everything. Yes. Just kidding. I mean, well, I mean, I guess we did with that code, right? So, if we run this. Yeah, that won't. We've just duplicated, which we expect, because we haven't changed any of our inputs. Yeah. We've duplicated our code. The only thing we've really changed is this, although we haven't hooked that up.
Like, yes, that's still hooked up, but it's hooked up to this. And so, we would also need to then, like, 52, 54, all of those variables need to be renamed. Like, last roll two, dice foul two, like. Are we doing this? Yeah. Yeah. We're going to go through it just to experience how not fun this is. So, you're saying that my first approach to all of this is actually wrong, because this is 100% what I would do, is come in and just be like, let me duplicate my code. It is not wrong. It is just not efficient.
So, it will work, and that's great. So, that's what we want.
Okay. And then, last roll two. Nope. Wait. Nope. Yep. Yep. Last roll two, find event dice. Is it dice foul? Oh, oof. I'm already lost. Dice foul 02. And that dice foul 02. And then, we'll just change 76 to dice foul 02, and keep the other variables as they were.
All right. This should actually work with both buttons. Yeah. Okay. Because we don't have to change the values inside the function, because it's really the label of this. It's the label of the reactive that we're accessing. So, we could have every variable inside our reactive, the same name, but what's going to affect it is this guy right here. Now, we could, if we wanted to change the max number, we could change that here, but we don't want to. Yes. Like, if we had a different number selector, then maybe we do. You know, select max number two, but we're both reading from the same max number. So, yeah. And as I said, with the reactive, the variable new roll is scoped to the expression of 54. So, it will not leak out, and so we don't need to rename it. Yeah. So, this works. This is awesome.
Going on a helper function adventure
All right. Recording done. Good. Now, what if I said, let's do it for 10 buttons and 10 outputs. Like, now this just gets icky, and it might work. You know, we could get clever in how, in, like, doing some square brackets with a for loop and a couple other things, but let's instead make a helper function, and I think that'll just kind of resolve everything. Okay. And this will be a little bit of a journey, but in the end, we'll come out on the other end of the tunnel here, because it's kind of like you need, you can't do it halfway. It's all in. So. I feel like a hobbit.
Yeah. This is definitely a journey outside of the Shire. Going on an adventure. Yeah.
So, like how we were doing other things with functions, like, oh, we see duplicated code. Let's make a function. Let's just go ahead and do that, and then just kind of roll with this. All right. So. Yeah. I'll let you drive.
Okay. Let's make a function. What are we, what are we, well, what are we functionizing? Like, are we going to take everything, are we going to take 36 through 81 and functionize all of that?
For this purpose, let's not do the output. Let's only do, like, 36 to 52. Okay. So, we're kind of doing 36 through 52. So, we are going to functionize one dice roll.
That's not right. Yeah. And we wouldn't need the return. Yeah. Yeah. Perfect. And then I just, personally, just go ahead and copy 40 to 56 and put it in, and then we adjust it from there.
Okay. So, one way that we could get around this without doing closures and things, because we know exactly what we're doing, just as an example, is we could pass in, like, a key, like a button key, and a max number key would be a nice one. So, just for fun, let's go ahead and go down that route and say, like, yeah, like button key and max number key. And we'll default max number key to equal quote select max number, like the name there. Yeah. Awesome. And then let's go ahead and use these, like, key values.
So, 99% of the time we do things, we are accessing input values via the dollar, but you can also access them via bracket bracket, square bracket, square bracket. So, let's update 40 and 42 to listen to the max number key variable. So, it'll be, like, input bracket bracket max number key.
And the reason we're doing double brackets is because single brackets will only return, these return a single item, whereas if there's more than one item, the single brackets will return all of those items. Yes. Yes. Single brackets returns a subset list, and double brackets returns the value for the key value of the list. Yep. I feel like this is our power, is that I use big amorphous hand-wavy terms, and then you're like, yes, the technical vocabulary that you're trying to use is. Yes. But it's good, right? If you want multiple, you do single. If you only want single item, you do multiple. So, we're selecting a single value, which we've already set here to our select max number.
So, our select max number is this guy right here. And so, that's like saying 13 equals max number key. Yeah, we are, we're good. This looks great. I'm following. Okay. Let's do the same treatment to 46 with button one. Yes. So, that'll be input of button key.
And do we need to change button key to, no, because if we want to generalize it, and we've got two buttons. Yeah. So, we'll go ahead and leave it empty, meaning like that's required. If you wanted, you could give it a default value, like for yourself, just that way you can run things faster. But I think it's good as is.
All right. So, let's go ahead and use our function. So, let's just look at the functions right now. Last role is scoped. Great. We then do a reactive where we listen to some max number, and we make sure that it's a new number compared to last role on 41. And then we return that new number. But that reactivity won't run until we have a button press, which is so we've bound that event. Great. And then we update the last role, given the dice value, whenever the dice value changes. Cool.
This is, yeah, yeah. That works. But it does need to be the last thing in the function, at least. So, okay, cool. This is good. And we can actually have multiple calls to one dice role, and we don't need to rename the variables internally. Like, they'll all just be constant, which is awesome. So, let's actually see if we can change the code below to use this method.
Ah, okay. So, we theoretically shouldn't need another last role.
And then, all right. So, diceVal. So, this is all wrapped in a function here. So, this is theoretically, let's see. Is this the same? I feel like this is the same code, but just not in a function. And so, here, I feel like if I wanted to rewrite this, I would do something where I would do, like, one dice role, and then my argument would be button 01. And then I think I can leave maxNumberKey as it is, because that's already going back to the UI element here. Seems fair. Seems fair. I never know if I'm walking into a trap with you.
I can't remember what the render text had. Can we look at that method at the bottom? Down here? Oh, it was using diceVal. So, it has diceVal, which actually didn't use last role, but we used last role as a placeholder. Ah, what we really wanted is diceVal. So, the 54 should be diceVal, not last role. That is my fault. That's all right. All right. Learning.
All right. So, we're going to do one dice role, and then my button key is going to be, okay, let me think about this. Because the input is already accounted for in my function, I only need to provide the button key, which is going to be button 01.
Oh, in quotes. And then I don't need to provide the max value, because that's already a default, so I can do it. All right. So, this is the dice role for button one, and then I'm assuming I could do one dice role for button two. Yep.
And then I think I can get rid of my second dice here. So, last role two, these guys, and this.
So, I'm assuming, would the next step be, I'll put dice01. Okay. What are we doing? Should we check our app? What are the next best steps? So, you have diceVal and diceVal02. So, one dice role returns a reactive, so we should store that to, like, diceVal and diceVal02. That one's, the label there on 54 is good, but we need to store the result of 58 into a variable. Got it. So. And for fun, we can say diceVal01.
And then we'll update 65 to be diceVal01. This should work. Kind of exciting. Okay. We have a history here. A famous last words. All right. Let's see what happens. Let's set that to 20.
All right. So, that's working as expected. Boom. Our code is already a little bit more efficient. So, we have functionized, that's a word now. We have functionized a dice role. With all of our reactives. And now we've got an output. Perfect.
Ready for rlang
Okay. Are you telling me we can make it more efficient? I think, well, make it more user-friendly is kind of where it is. So, I like this. This answer isn't bad. It's a good stepping stone. I would expect most people to be able to create this function and to execute this function. This, where we're going to go next is a little bit more into the R lang, into closures, into expressions and things that don't make sense right away, but they're super powerful. But that's the R language. We are choosing our pain now. We are choosing the user benefits. Yes.
We are choosing our pain now. We are choosing the user benefits.
But we've done a lot of the heavy lifting already. Like, we already have a function that's there. It's just, I, like, 58, when we look at it, it's like one dice roll of quote button 01. I now have to remember that that represents input dollar button 01. Like, that's a little much. We're normally, we're used to writing, like, reactive input dollar button 01. Like, that's more familiar for me. So, yeah, I think, I think it'll work. Let's go. Let's do some closures. Let's embrace the closure fun. Closures.
So, a little bit of safeguarding. Let's actually make a reactive between 37 and 39 that is, like, max, like, yeah, like, max, max number or something like that. And then we'll just do our input max number key.
Awesome. And then we'll replace input max number key in 44 and 46 with max number. Oh, okay. 44 and 46. So, while you're doing that, there is no computational, like, speed up by, like, input dollar whatever is a constant static value. So, like, calling it twice isn't a big deal. Reactives do, we have this, like, soft caching built in where, like, we don't recalculate everything twice. So, they're kind of equivalent, but it gives you a little bit more freedom down the road from where we're going with this code. 44, 46, max number need to execute because we need to retrieve the max number value. Awesome. Great. So, let's test this just to make sure this works.
And then we can click button, click button. Awesome. Great. Okay. So, closures. Let's look at I really like this max number thing here in 39 and 40 to 41. I think this is, like, a very isolated, quick, and easy thing to do. Let's also add something, like, in the function up at the top.
Let's add, like, instead of max number key, maybe we say, like, max number expression. Okay. And I don't know if you want to, yeah, let's do, like, just expr, max number expr. Great. And let's go ahead and get rid of the default value because that is not what we want.
Oh, let's change our function call down below of one dice roll and have down beneath, sorry, like, in 60s, 70s, wherever that's at. Yeah. Great. 60s. Let's say the max number expr equals input dollar. What is it? Select max number or something?
So, what was the code that we had before when we wanted to retrieve the max number? What was that code? It was something like input dollar. Yeah, it was input dollar select underscore max underscore. Max number. Great. Let's put that there in, like, the second argument to the function.
So, you're saying input and then we'll do, I literally just said it. Yeah. And we can do dollar select max number because we're using it directly, right? So, like, this is where it gets interesting. Like, if we were to look at this as a function, we could be, like, if I'm interpreting this as is right now, what this would do is it would take the max number value right now and it would never update and it would never listen to anything if it was just a static value. We would say, oh, the max number is zero and we move on and it'll never hit any reactivity. Like, this is how normal functions would operate.
But what we want is we want to have this expression be used at a later date and to allow Shiny to re-execute that reactivity over and over and over again. And I think this is just, like, really neat things. So, let's move back up to the reactive. Yeah, the max number reactive. And we now have the max number exper. Cool. So, if we were to, let's go ahead and duplicate 39 to 41 so that we have what we had before. And then this is our new stuff. So, like, natural tendency right away is to go ahead and let's say, let's replace input bracket bracket max number key with our expression. So, max number exper.
No, just the whole line because max number exper equaled input dollar select max number, right? So, we're just kind of swapping it in. And now we're kind of looking at it and it's like, okay, so we have a reactive and it's saying, like, retrieve the value of max number exper. We'll I have the value. It's not really a reactive expression because if it's a function, right, the reactivity is cut immediately, typically, and you just receive the value and nothing is going to work as you want. And that stinks.
So, this is where we are wanting, there's a thing in RLA enclosures. Do you think it would help if we pulled up the docs? Is that easy to do there? All right. So, we are in the docs. We've got some metaprogramming patterns and we've got a little bit of injection. I see some of our favorite words here. I think most people who are app developers are familiar with dplyr. And there's this wonderful problem with, or not problem, but situation that occurs with dplyr in that what if you want, I don't like, dplyr does this, like, delayed or not delayed, I can't remember the name, metaprogramming, right? So, because you have the ability to ask for column names directly, what if you want a variable to represent a column name that you're asking for directly?
And this gets a little difficult. Like, either you have to subset from your calling data frame, which is not what we want to do. And so, they have, with RLA, we're now able to do this using these forwarding patterns, such as diffusing and injecting. And I am very familiar with the, it's called the bang bang operator, or exclamation point, exclamation point, paired with enquo, because I like to handle these expressions very directly. So, enquo, I would grab it, and then I would replace the value using bang bang. And that is how I'm familiar with it. But if all you do is you immediately do a bang bang enquo, and that's all you do with the variable, then you can use these double curly brackets. And these double curly brackets is embraces, or embracing, and it's just a shorthand version of bang bang enquo.
So, we're going to steal this and use it within Shiny, within our function. All right. Recently, Reactive and Observe and a few other methods gained support for closures. And so, now we can use these forwardings and embracing and whatnot. Let's do it.
!! + enquo()
So, what I like to do at the very beginning of a function is to enquo all the expressions that I don't want to evaluate now, but I want to evaluate at a later time. Are these, like, promises? Say again? Are we creating promises? According to the R package promises, no, we are not. But closures do feel like a promise to evaluate something, but it kind of grabs the expression and the environment where that expression came from. And then you can pass it along the both of them, which usually somewhere along the way, in the olden times, one of them would get lost and not a good thing when that occurs. And closures bundle it up into one and make everything much better.
So, let's go ahead and after 35, we'll say maxNumberQuo equals enquo of maxNumberExpert. So, maxNumberQuo... We're gonna do a brand new one? Yeah, max... Yeah, brand new variable name. Yep, maxNumberQuo. And we'll be the enquo and of maxNumberExpert.
So, enquo is a method that will turn your argument name into a closure. It is specifically meant to be called inside a function. If I was just in a script, I might just use quo, but inside a function, it's enquo. Okay.
Is this like packaging it up in its own little special environment? Say again? Is this like putting maxNumberExpert in its own little environment and just saying, go sit on the shelf over here? Yes, and it won't evaluate it. Okay, so it's like there, but it's all bundled up on the shelf. Yep. It's for like later. It's like, oh, before your birthday.
And let's go down to 46. Okay. So, 46. Reactive is also something that's expecting an expression. So, it's not something that we could even change it to maxNumberQuo and it would work because we still have this issue of like reactive will just execute the expression that is there, which is saying like retrieve maxNumberExpert. And that's not what we want. We want it to actually, it's very subtle, but we want it to execute the quosure or execute the quosure expression is what we want.
So, to tell reactive that I want you to use the contents of this variable and not the text representation of this variable, we will use inject with bang bang. Okay. And the inject will go outside the reactive. Bang bang will stay there. It's the way I described it. So, you're saying inject the reactive? So, yep. And then let's go ahead and take the curlies off of there just to help us think that I'm now injecting the expression. So, I'm not giving an expression, I'm injecting an expression, but it's not the expert will do the quo, the maxNumberQuo. And the bang bang is like unwrapping our present. Is, yeah, it's unwrapping the present, but not evaluating it yet. So, it's like a cool toy that we've unwrapped, but cannot yet touch.
Yeah, I think to kind of go with this present is nQuo, put it in a shipping box. Okay. And it's shipped to us and now it's unwrapped and it's available for reactive to use. Okay. And inject, put it in there, like it snuck it in.
So, let's go ahead and comment 4244. And I think this, and did we replace maxNumberExpert in both locations down below in the one dice roll calls? Awesome. So, I think, I think this will work. No! Oh, no. All right. Oh, library rlang. You know, the number of times that this has gotten me lately is kind of surprising.
All right. Are you ready? All right. Let's see what happens. Let's set this to that number. Okay. Look at that. Sweet. That's a really weird dice. And that would have to be a, that's a good teaser. How large would this dice not have to be? Like, it would have to be massive to have that many sides. Be like, roll the atom.
So, let's, let's go down to our function calls that we, that we were making. So, it works, right? Yeah. And it's, I think it's a lot more representative of what's happening. So, the user is saying, here's my reactive select max number.
Let's actually change this a little bit just to show that this is an expression that's being done. So, for dice foul one, let's go ahead and put it in curlies and say, just the input select max number, just that part. Oh, okay. And, oh, single curlies. I know. I just got really excited. Can't embrace yet.
So, let's do select max number plus five or something like that. Okay. Something bigger. And then let's rerun the app and we should get, the first dice should have values up to eight. Okay. The second dice should only have values up to three. So, we've got seven. I just want to see if we can hit eight. I mean, we might not. Laws of probability, et cetera. It's, oh, oh, there we go. And then this will only be one to three. Yeah. Look at that. We did a little math with our expression.
And so, this now gives the caller much more freedom as to what they want to do or things like that. And the function logic is all hidden away. We can put it in the R folder. We could, you know, source it from somewhere else, maybe a different package. Um, but this one dice roll is now available. Yeah.
I like it. So, let's actually do the same treatment with the button expression. Okay. This guy? Yeah. So, instead of button one, um, it, we should turn it into something like, uh, input dollar button one. Okay. Yep.
And same with 71. You want to do both here? Let's do both. All right. Great. And one dice roll. Um, let's see that at the top. So, that's button key. So, let's turn that into button expert. And after 38, we will end quote button expert and store it as button quote. I like just encoding everything at the top. That way, you know, you know, what's available.
And then, uh, yep, we can delete that. And our button quo can be injected in 53. 53 is where we're going. Okay. So, we could just do, instead of, so it would be bind event, inject, bang, bang, button quo. And inject has to go outside the bind event. It's never going to happen for me.
Yeah, it's a little, little off. And then, to throw in a little more wrinkles, uh, the pipe operator from 52 is actually, we want it to be for the bind event, the internal parameter, not for the inject. So, we should pipe to a curly bracket. And, uh, that way, yep. And then, so now, this is a McGridder expression. And so, we can bind event to dot, comma, bang, bang, button quo, dot, comma. Yeah. So, the dot represents whatever was passed into 53. Yeah, that, that reactive. And this should work.
So, reload app. Hopefully, it doesn't break. It did not. All right. So, the first one should still go up to eight. Great. Much more than three. Saw a couple fives in there. Great. Perfect. All right. We did it. We did the thing. We did the thing.
Benefits of the rlang approach
This, like, it, to me, this is a, like, a big journey. But then, the possibilities, you just unlocked everything. Like, now, the, the user can be like, oh, I want to be a button and something else. Or, I want it to be a list of things. Or, there's all sorts, like, I can now, as a user, provide many things for this expression. And not just a single key can be very limited to input dollar or input bracket bracket. Yeah. So, you've expanded the scope of what can be done with the same amount of code. Like, you would add, other features would require more code, but not necessarily more calculations. Correct. Absolutely.
Embracing the embrace operator
Um, so, the joke comes full circle. Let's embrace it. Yeah. We are only using the expressions in one location. And we are only, we end quote it, and we call bang bang. So, let's use the embrace operator instead. And then, we can just kind of get rid of the intermediate operations. So, we're, get rid of these. Yep.
All right. And now, do I still need inject? Or, is inject handled by my double curlies? You still need inject. The double curly is going to be the replacement for the bang bang and the end quote call. So, whoops, nope. Yeah. So, the embrace operator is a double curly, similar to the bang bang. You know, it's something that rlang looks for and has overwritten. And max number quo doesn't exist anymore. So, it needs to be max number expr. What it originally represented. Great. Then here, I'm going to do this guy, and it's button expr. And then, do it again.
Nice bang. I think the formatting is to put a space, but I'm not entirely sure. So, we're just going to go with it. It's fine. I like it. It's clear. It's very clear that something is happening. Like, it kind of calls your attention to it. Like, something's happening that's maybe not what I expect. Yes. Unless you do this all the time, in which case, you expect it. Even then, even then.
I think this will work. Um, let's run it. Run it! All right, let's see. We've got a six, we've got an eight. Yep, looks like everything there is working. I'm gonna, I'm gonna actually jinx us and say, I don't think we've ever had a session where the app has run so smoothly each time. Yes. It's also super helpful having a working app to start with. This is true. So, we were altering a good app versus creating it from scratch. That's true. That's true.
All right. So, are we, like, is this, is this it? Are we now, checkbox done? Can we make this more efficient? Or is this kind of... That's, that's it. That's, that's the journey. All right, we made it. I don't, I don't know the Hobbit story very well. So, we like, went out and then we came back, right? Like, they make it back. Yeah, they, they make it back. They make it back. Luckily, we didn't have to pit stop at Mordor and restart everything.
Visualizing what's happening using reactlog
If we have a minute or two, let's go ahead and add React Log so we can see what's happening. All right. It's, I mean, it's, it's one of my packages. I gotta, I gotta promote it. Who made React Log, Barrett? This wonderful developer. His name's Barrett. Yeah.
So, let's do two parts. So, we have a, we did a library up top, right? And let's, I like, I mean, for developing purposes, let's go ahead after 31, say React Log module UI. And we'll also do something near 35 as well. So, it's all good. That's right. Right, because you put it in both places. Yeah, so we need a, basically an iframe into the React Log. So, it's React Log colon colon. React Log, is it not installed? No, it is. I just did it. Oh, there we go. Module UI, the fourth one there. Great. And defaults are good if you're only using one. So, that's good.
And then just inside the server, I like to enable React Log. So, React Log colon colon, React Log under module under server. Great. And things should line up with defaults. That's awesome. So, let's go ahead and reload the app. So, we can see all the reactivity that's happening. Making this larger for a reason.
Oh, and we need to enable React Log at the beginning. So, yeah, like after line four, we can say React Log colon colon, React Log enable. Oh, just that? React Log under enable. All right. Yeah, and that does all the settings for you. So, that way it's just ready to go.
All right, let's look at this gorgeous little app, React Log here. So, this is, we have done nothing. And this is what we've got. Let's go ahead and actually click each button once. And then up top, you might have to go to the side. Your face is in the way. Ah, yeah. So, let's click the roll dice, roll a second dice. And then let's refresh now. The orange refresh. Yeah, there we go. A little bit more going on. Look at all the steps. Step 27. Yeah. All we did was click two buttons.
And the 27 is actually what it took to get to an idle state. So, this isn't actually even where we finished at. So, this is right before you clicked the first button and then the second button. So, let's actually step through with that arrow. Yeah. And we can just click a couple times. So, we clicked button one. It's now invalidating everything downstream. Cool. Then the dice roll is trying to update. The bind event is listening, getting the max number that was selected. Things are updating. Great. We're now going through on the execution of it. And then, good. So, that was one. That was what happened with the first button click. Yeah. And then the output of the dice value is also updated. And now the first button click is done. Okay. Similar thing will happen again with the second button click. So, it'll be the group below. Oh, so we're seeing the exact same thing.
But what's really cool to me is things like it's using input button two. Even though that was the expression that we had in the function, that was passed through to the reactive. Or like those different things that were done were passed through. So, I think it's nice to see how that's occurring. Yeah.
What's a little funny to see is diceVal is just hanging out. Nothing's connected to it. You know. What is, what's happening here? So, that is actually, that reactive is, we can't alter the reactive in place. Because it's something that is not, like you can't, you can't alter the variable in place safely. So, when a reactive is sent to bindEvent, a new reactive is created. And so, that's why in the middle, there's a bindEvent of diceVal to inputButton. You can't see the full name. But to inputButton2 and to inputButton1 if you go to the one above. So, that actually replaced diceVal. I think it's pretty neat to see that that's what's occurring underneath the hood. Because you did create the diceVal reactive, but we tossed it away when we called bindEvent.

