How do things compose across domains?
This is an episode of Thoughts on Functional Programming, a podcast by Eric Normand.
A few episodes ago I talked about how things compose. But I didn't get into how things compose across domains. For instance, how do actions compose with calculations? Let's get into that, and what it reveals about our work as functional programmers.
Eric Normand: How do things compose across domains? Hi, my name is Eric Normand. These are my thoughts on functional programming.
A couple of episodes ago, maybe four episodes ago, I talked about how things compose. Just to review. When two actions compose, it creates a new action. You can compose either in sequence or in parallel, calculations, compose. They create a new calculation.
That's basically, function composition as it's normally defined. I like to be a little looser and say you can actually have a calculation that the output of that calculation goes into any of the arguments, not just the first argument of the next function. Two pieces of data can combine, can compose. That really depends on the data structure.
Stuff can compose into a tuple or into a sequence or into a HashMap with the keys and values being the bits of data. What I realized was I didn't talk about how things composed across domains. What happens if you have an action and you compose it with a calculation?
Let's go over that now. We'll have to review the three domains and what they mean, so actions real quickly, there's a rule of thumb, it's called time or times. Does it depend on the time it is called or how many times it is called? If either of those is true then it is an action. Sending an email twice is different from sending it once. Calling that twice has a different effect.
Calculations don't depend on the time. They can calculate a value based on the arguments. They are opaque, meaning you don't know what it's going to do until you call it, till you run that. Then finally data is totally transparent, self-describing, self-identical. Meaning it is what it is and you can interpret it in multiple ways, doesn't run. It's totally inert.
What happens when you compose an action with a calculation? This is something easy like you want to uppercase a string that's totally a calculation. It doesn't matter when you call. It's always going to give you the same answer. You're doing uppercase string and then you're going to print it out.
Printing it out is obviously an action because if you print it out twice, it's different from printing once or zero times. We're doing a calculation and then an action. We want to compose those up into something else that does both of those things.
Here's the question. What is that thing? That's like print uppercase. It obviously also depends on the time or times, because it's still printing. That thing that you compose the two into is an action. You'll see if you, just by thinking about a lot of examples, anytime you have an action and you compose it with anything else, it's going to be an action.
You can't lose that dependence on time. We see that anything composed with an action, anything meaning, any calculation or any data composed with an action is going to lead to another action. Similarly, we could ask what happens if we compose a calculation with data.
This is called a Clojure normally where you have some data that's in the scope of the function. Now, this is part of that function for good. It's going to influence the calculation, but, of course, that can't change, so it's still timeless. It's still a calculation. It's totally opaque like other calculations.
It's not self-describing. It's hard to define equals for it, like data is very easy to describe equals for. You have data plus calculation is going to give you a new calculation. If you arrange things in that order. The order that things progress to. Meaning you look at data plus data is data. Then data plus calculation moves up to this other level.
Then calculation plus action, all that moves up to the actions. We have this kind of migration. Maybe we should turn it up the other way, so that stuff is going down or tends to move down. From data, it moves to calculations and then from calculation, it moves down to action.
This is just a natural tendency because there's no way to remove the time except by effort. You actually have to change your code and refactor stuff to extract out the parts that aren't actions into calculations, whereas it's so easy to just put two things together and now you have more actions in your code.
There's this natural tendency in our code bases, even a functional languages code base, even something strict like Haskell. There's a tendency for things to slip down into actions. Also, stuff in data will slip down into calculations.
It's just a natural tendency because we like to compose things and things are just going to move down that way. One of the things that we have to do as programmers to remain vigilant too, is that we always want to counteract that natural tendency by pushing stuff back up this ladder into data.
That explains one reason why functional programmers are so reticent to use actions. It's because they start to infect everything. It's much better if you could avoid that, to begin with.
All right, thank you. My name is Eric Normand. I'd love to discuss this further. You can reach me on Twitter, I'm @ericnormand. You can also email me at email@example.com. Awesome. See you later.