How can pure functions represent state change?
This is an episode of The Eric Normand Podcast.
Subscribe: RSSApple PodcastsGoogle PlayOvercast
Pure functions have no effect besides returning an immutable value. If that's true, then how can we use them to represent changing state?
Eric Normand: If our functions are pure, meaning they don't change anything outside of their scope, then how is it that we can use functions to represent change and to represent behaviors? Change over time?
Hi. My name is Eric Normand, and these are my thoughts on functional programming. Let's imagine that we're already good at making pure functions, doing a lot of work on our calculations.
We're good at making pipelines, all that in-the-weeds functional programming stuff. Very important, but let's say we're already good at it. Why are we even using functions? What can we do with these functions now that we're good at them? My answer is that we should be modeling behavior with functions. Let's back up a step.
what it means to change
When I was younger, less experienced at programming, and first introduced to Java, if one of the requirements said that, "Well, this thing needs to be able to change over time," I would probably just have made a setter method for that thing and just said, "Well, this is how you change it. You call this method, and it will mutate this value. It will just set it to something else."
I think that that is about as much analysis as I ever did with what it means to change. I don't think I'm alone in that because I hear people all the time asking like, "Isn't the world mutable? Don't we need mutable state? Because the world is mutable, how can you model things when things change? You need changeable things."
I think that that misses the point. The point is this. If you just say, "Things change," you haven't really captured anything interesting. You haven't said anything specific about how they change. When do they change? Why do they change?
You have this situation where we've got this general memory where a byte can take on any value between 0 and 255 that you want to put in there. You're not constraining it at all based on the semantics, based on your domain, based on what you want that byte to represent.
how things change
What functions can let you do is define how things change. How do they do that? One way is because functions take an argument and they return a return value. We're talking about pure functions right now.
They have an input and an output. They don't change their input. We're just not dealing with any mutation at all, pure functions. If you make that input the current state, the current value of the thing, you can make the output the next value of the thing.
You're defining how it changes very precisely. It goes from this current thing to this new thing. Now, you can add another argument and do something like make that argument an event.
You say, "Well, when the state is like this and we get this event, then what is the output? What is the new state?" Or maybe it's, "What is the new state?" You also turn another set of events that get called. They get dispatched.
test properties of the return value
This is the kind of modeling that we're able to do in functional programming, is take this top-down approach of saying how the entire state changes based on the events we're getting.
You're defining it in such a way that it's totally testable. You don't have to set anything up. You just pass in a current state and an event, and you can test properties of the return value.
There's nothing like, "Do I have to make a new mutable thing and like fiddle with its methods until it gets to the right state, and then test this one new method, and then check if it changed?"
It's much more testable, but then there's this other thing. There are so many questions and so many ways of thinking that mutation just doesn't really allow us to do. You have to do a lot of work to work around.
One of those — I think it's the best example — is to think about counterfactuals. Let's say, we do have a real current state. This is the state of the system. The state of whatever thing, entity we're representing, whatever it is.
We want to ask, "What would this thing look like after I receive this event? I haven't received it, but what would it look like if I did?" That's a lot like what a test is. It's a counterfactual.
It's not an actual production data. It's not an actual production event, but we're asking, "What happens in the system when I do this, and I expect it to look like this?" It's a test. It's often also very useful in your production system.
all the information you need for the screen
What would happen if I sent it 10,000 of these events? Would it overflow? One thing I did as an example is I had a state of the current screen. This is all the information you need for the screen, including the title of that screen and what back button to have on the top.
How did I figure out what label the back button would have? It didn't just say back. It had the title of the previous screen. I actually ran the behavior function with the current screen and the back button event.
Then I would get back the next screen, which was the new screen, which was the back screen. I didn't change the actual screen. It's just the state. Then I can read off the title from there.
back button label
These are the things that become just easy when you're dealing with pure functions. You've defined your behavior in your functions. You can imagine me doing it a different way, which would be the current screen has a mutable field called title. It has a mutable field called back button label.
I would have to go and set them manually from somewhere else and rely on the sequence of steps that got me there to calculate what each one should be. As opposed to representing the entire thing as data and then a behavior function.
This is the thing I'm working on for the book. I've been talking with my editor about not...I don't want to reproduce a lot of the work out there. There's a lot of good work actually on the functional programming in the small like how to do currying, how to make data transformation pipelines, map, filter, reduce, all those things.
Avoid getting down in the weeds there, but then what is there to talk about? I think it's this kind of stuff. I've been reading books. No one is talking about using functions to model the behavior, which is something we do. It's not like I'm making it up, but I've got a grip on it. I think it's high-level enough to fit right in the book.
My name is Eric Normand. This is "Thoughts on Functional Programming." If you want to get in touch with me, I love hearing from my listeners, answering questions. I'm @ericnormand on Twitter. You can also reach me over email, email@example.com. See you later.