Why side-effecting is not all bad
This is an episode of Thoughts on Functional Programming, a podcast by Eric Normand.
We run our software for its effects, so effects are necessary. We can't write 100% pure code. But I contend that some effecting code is better than others. In other words, there is a spectrum from bad effecting code to good effecting code. Even if you can't turn an action completely into a calculation, you should still strive to minimize implicit inputs and outputs.
Eric Normand: Why is side-effecting a spectrum and not a binary choice? By the end of this episode, you should learn how to improve your actions, even if you can't turn them into calculations. My name is Eric Normand. I help people thrive with functional programming.
We all know that functional programmers prefer calculations. You might also hear them called pure functions. We prefer pure functions over a side-effect. We prefer calculations over actions. Those are my terms.
But it's not always possible. You need actions. That's why we run our software. If you need to send an email, because that's the affect your software's supposed to have, then you need to call an action. It just needs to happen. We need to examine that a little bit.
Let's imagine we have this action. It's a complex action. Let's say, you didn't write it. You just found this in the wild. Someone else has written it. It reads from a global variable. It writes to the database.
We don't want to change the behavior of this app. We need to write to the database. That's an important thing. Let's just assume that. That database value needs to be written there because some other part of the app needs to read it in later. We can't get rid of that call, but we can get rid of the global. That is not necessary. We all know that global variables are not necessary to the outside world, to the behavior that we're trying to get our app to have.
Here's the question. Seeing that this is already in action and there's no way around it, should we get rid of the global variable? The global variable is not going to change this action from an action to a calculation, if you get rid of it. It's already an action. It's already writing to the database. Getting rid of the global is not going to change the category that this function is in. Should we get rid of it? Is there a point to getting rid of it?
I say yes. I say, if we do get rid of that global variable read — That read is a global variable, so that this is, let's say, "More pure." It's not pure, but it's more pure — that this action will be better. It will have fewer inputs. These would be, I guess, implicit inputs. The global variable's value is an implicit input, as opposed to the explicit inputs which are the arguments. It still has the same number of outputs. We're not changing that, but it has fewer implicit inputs. I'd say it's better.
What this implies, if this is true, if what I say is true. I think a lot of people would agree with me, just to set aside. I think global variables are known to be problematic, even in a world that does not recognize the distinction between actions and calculations, but let's just put it in a conditional way.
If you get rid of the global variable, the action will be better. If that's true, if the action is better, that means that there are better and worse actions. That means that it's a spectrum. That side-effecting is a spectrum. That you can have something that's more side-effecting and less side-effecting.
The way I like to think of it is, this function that is outputting to the database, it's writing to the database. That is an "implicit output." I'm putting that in quotes, for those listening. It's an implicit output because there's only one explicit output to a function, which is the return value.
This writing to the database is data leaving the function and being used somewhere else, just like a return value is. You could look at it like, "I'm not going to have a regular return value. I'm just going to have this one return value that's implicit, but it's just one." There is some constraint on it that is similar but different, but it's similar to how you say, "I'm just going to have this one return value."
This is similar to how I like to think of React. The whole React system is not pure because it is modifying the DOM. React lets you write a function that takes some input, then returns a representation of the DOM, and then it does some diffing between the DOM and the actual DOM.
It figures out what needs to change in the DOM, and it makes that change. It's supposed to be pretty efficient at it but the end result is, it is outputting DOM changes. It is side-effecting, depends on when you run it, and it is a visible change to the world. It's not just the return value.
However, it doesn't even have a return value, not the whole system. That is its only purpose, is this output to the DOM. I'd like to look at it like, "They're working within the language." The language gives them only return values, and side-effects are all implicit outputs. That's all they have given to them.
They've constrained it in such a way that it's still easy to reason about, in the same way that functions that only return from the return value and don't have any implicit outputs are also easy to reason about. This thing that only has one output, which is to the DOM, that is also easy to reason about.
This function that only writes to the database, it could, in a sense, have that same ease of reasoning about it. It's not as easy as a pure function, that does not modify anything, that you could run it a million times, and it wouldn't have any affect. It will have an effect if you run it twice.
It will change things if you run that same output twice. It's still not unconstrained, as in its reading and writing the global variables left and right. It's got just one output, which is to write to the database. I think there's something to that. There's a gradation of side effectiveness.
There's better and worse actions. The worst actions have a lot of implicit inputs and a lot of implicit outputs. Then as you go to the better side, you reduce those numbers until you're down to one implicit output or one implicit input.
It's still an action. It has to be because you still need to do that thing that read from the database, write to the database, send the email, contact the server. Whatever it is, you still have to do that, but you can reduce it down to where it's a unit of just doing that one thing. That's better. It doesn't cross the line into calculations, but it is close to that line, as close as you can get. There's a lot of value in that.
That's the moral. Side-effecting is a spectrum. The more we can move towards the line, even if we can't cross over into calculations, the better. That seems reasonable to me, as a design principle, as a functional programming coding principle.
If you have enjoyed this, you can find other episodes and this episode on lispcast.com/podcast. There, you'll find all the episodes with video, audio and text transcripts, if you like to read more than you like to watch or listen. You'll also find links to subscribe, and to contact me on social media. If you like, please go there. I'm really into discussions, so if you have any comments, questions, disagreements, let me know and we'll talk. Awesome. Rock on.