Level 1 of functional thinking is to distinguish between actions, calculations, and data. But what is a calculation? In this episode, we go over what it is, how to recognize them, and how to implement them. By the end, you should understand why they are so important to functional programming.
Eric Normand: What is a calculation? By the end of this episode, you will understand how to represent timeless calculations in your language. My name is Eric Normand and I help people thrive with functional programming.
This is an important topic. I have a three-level system, three-level scheme, for how you can progress in your functional thinking journey. In the first level, the most fundamental level, you have to make a distinction between actions, calculations, and data. This distinction is necessary for the next two levels.
I’ve already gone through actions in another episode. You should look that one up if you’re interested. In this one, we’re going to go over calculations. We’re going to go over what they are, how to identify them, their requirements and how to implement them. Let’s get started.
As a rule of thumb, I like to say calculations are runnable code. They’re computations that do not depend on when they are run or how many times they are run. That doesn’t explain much. It’s a rule of thumb. It’s a way of identifying them. We’ll come back to this a few times in this episode. They are the kind of mathematical function that you think about.
Another term that we hear a lot in functional programming is a pure function. It is a function from inputs to outputs. It’s a computation that takes some inputs and returns an output. It doesn’t do anything else. It doesn’t send an email. It doesn’t change any mutable values in your program. It simply does a calculation.
When you have one of these, because it doesn’t matter when it’s run or how many times it’s run, it’s always going to give that same answer no matter what. They’re easy to test. They’re easy to understand how they work. You don’t have to look at the whole program and the history of the program to understand how it works.
If you can say, “Hey, this one doesn’t depend on when it’s run or doesn’t matter how many times I run this. I’m always going to get the same answer.” Then you have a calculation. Now it can get tricky because calculations aren’t always functions. That’s why I don’t use the term “Pure function”.
It is not a function. It’s a piece of syntax that the compiler recognizes and converts into machine code or whatever, it interprets it. It’s a different way of interpreting from interpreting functions. That is one reason why I call them calculations instead of functions. Is because function already has meaning to most programmers.
It’s unfortunate, it’s sad that it’s not the same meaning mathematicians have. It would be better to have a more accurate usage of these different terms but that’s the way it is. Pure function gets at it, but then again it’s function. What about plus? What about times? These aren’t functions in most languages.
One plus two is always going to be three, right? A plus B is always going to be C, if you have the same values of A and B. It does make it easy to reason about. The problem is, that operator is not first-class. It cannot be passed to another function.
The reason you need to do that is because at level one you start doing what are called, “Higher order operations.” You start doing Map, Filter, Reduce. You start building abstractions that take functions and return functions. You have functions that operate on functions. You can’t do that with Plus.
What you need to do in your language is to find a way to represent all of those things that are not first-class baked in and represent them as a first-class thing. That is one of the challenges of doing functional programming in a non-functional language.
When you do that, you now have a function that does plus, it does addition but it’s first-class. You can start doing level two things. Did I say, “Level one?” Yeah, first-class is definitely level two. I got to change my notes here.
In something like Java, it becomes a little bit more difficult. You could look at a method, let’s say it’s a static method and it does not access any mutable state or anything. It just takes some values as arguments and return to value as a return value. That is a calculation but it’s not first-class.
You get all the benefits of it being a calculation, meaning it’s easy to test. It’s easy to think about what it’s going to do. You don’t have to worry about when it’s going to be called or what order it’s going to be called in. It has all those benefits. What it doesn’t have is the ability to pass it as an argument.
Maybe with the new Java 8 stuff they’ve changed this but when I was learning Java, you could not pass a method into a function as an argument or a method into another method.
The only thing you can pass in besides the primitive types like int and stuff, you can pass in an object. You need to represent your function as an object. I think with the lambda syntax in Java 8, you can start doing that. You can represent a single method call as a lambda.
This episode is not about how to do it in Java. That might be a different episode or you might just have to do that on your own and figure out how to do that. To get to level two you need a representation that is first-class.
One thing I like to say about calculations that I think might be clearer now is that they are timeless. Actions are bound up in time just by definition. When you look at any action, you can say one or two things. It either depends on when it is run. Meaning, compared to other things that are running, there is going to be some order where it has a different output.
Now, is that good or bad? I’m not judging. I’m just saying we put those in a different bucket because they require different kind of attention. Those are actions. If it doesn’t read any global variables, if it doesn’t read from the network, do anything like that, all it does is it compute something from the arguments to the return value, then I can say that it’s timeless.
It doesn’t matter when it’s run. I could run it on a different machine. I could run it right now. I could’ve run it two weeks ago, and cache the value, and just kept the value. It’s timeless. I can free myself from worrying about any issues of time. Can I run it 100 times? Yes, no problem.
I can’t say that about sending an email. A function that will send an email, I can’t say that it’s timeless. If I send the email today, it’s different from sending it tomorrow. If I run this function five times, it’s different from running it one time or running it zero times.
Calculations are these things that because of these constraints we put on them, it doesn’t matter when you run them or how many times you run them. Then we can free our mind and just say, “These are an easier bucket. This bucket is easier to deal with. We can actually pay a lot less attention to them. They’re easier to test too.”
You can just treat them in a different way. It’s like having the difference between a wild animal and a tamed animal. The tamed animal will just sit there and listen to you. If you say, “Hey, shoo, shoo,” it’ll just go. Whereas a wild animal, if you say, “Shoo,” you don’t know, it might jump out at you. They’re in different buckets. Cool.
I’ve kind of belabor this point. That’s what I mean by timeless calculations. They’re timeless because they’re not bound up in time. Whenever you run them is fine. You don’t even have to think about it. This is what functional programmers do, is they put more and more of their code into calculations.
They still have some actions. You need action. You need to send that email, but the more we can put into calculations, the more relaxed we can be because this stuff is much easier to work with. It’s an easier medium, these things that don’t matter when you run them.
One of the hardest things about software is getting things to run in the right order. If you have a whole bucket of things that it doesn’t matter what order they run in, it makes everything easier. It means you can cache them.
Also, another word for that is memoize. There’s a whole episode on that. It means you can even figure it out at compile time. You can do lazy evaluation. Maybe you don’t ever use it. Maybe you never need that value. You could just not calculate it until you really need it.
There’s all sorts of great stuff you can do once you are free out of time. You’re freeing yourself from being bound in time.
OK, let’s recap. Calculations are computations from inputs to output. They don’t do anything else. The rule of thumb is, it’s a calculation if it doesn’t depend on when it is run or how many times it is run.
We’ll also call these, very often, pure functions, but they’re not always functions in your language. Some operations in your language are operators and not functions. It’s possible, or they’re a static method.
What we need to learn to do in our language — and every language is going to do it differently — is to convert these things into first-class objects, first-class values. That’s so that we can do level two stuff, which is like Map, Filter, and Reduce that kind of thing.
I like to say that they’re timeless. They represent like mathematical relationships, addition. It’s timeless. It’s never going to change. It’s been that way since who knows how long. The beginning of the universe, I want to say, but I wasn’t there.
No one knows if the laws have changed, but as long as people have been around, two apples plus two apples is equal to four apples. It’s just always going to be the same. Cool.
This has been my thought on functional programming. I’m Eric Normand. If you want to find the other episodes, the past episodes, you can find everything at lispcast.com/podcast. There, you’ll find audio, video, and text transcripts of all of the episodes. You’ll also find links to subscribe, whether you want to subscribe on YouTube, or the podcast, or via RSS if you like the whole blog thing.
Also, you’ll find links to find me on social media. Please get in touch with me. I’m looking for the others. All right. Thank you very much. This has been my thought on functional programming. Rock on.