A Theory of Functional Programming 0001
This is an episode of Thoughts on Functional Programming, a podcast by Eric Normand.
OK. I'm trying this out. I'm writing a book. I'm going to move. I'm writing a book. I think I'm going to call it, "A Theory of Functional Programming." Let me explain. Here is the reasoning. That's bad lighting hold on. I'll explain what I'm doing and I'll explain the reasoning behind the book.
I want to write this book. Writing a book is hard. It takes a lot of time and energy. You have to develop the ideas and type all the words, and do everything. Any help that I can get to do that will be welcome. Someone suggested to me that I talk to someone or I record myself talking like in the car and then transcribe it.
It's not going to be great text or anything. It'll be something to work with that will alleviate a lot of the typing I'll have to do because editing is always easier than typing.
We have a social part of our brain that helps us organize our ideas. We're able to think about what the other person is experiencing in a way. Maybe when you're writing a book, it's good to do that. It's often easy to slip out of that.
Just start writing very technical and abstract. Basically what I'm saying is, it's easier to talk than it is to type. Somehow we can talk for hours and not really get tired of it but typing for hours is tiring.
Why the book? Functional programming has this very academic feel to it right now. When you do a Google search, all of the definitions, you search for something specific. You search for what is functional programming.
You get a lot of definitions that basically boil down to functional programming is programming using pure functions. Your whole program is a function from inputs to outputs. I've been doing functional programming, professionally since 2010. That's when I first started getting paid to do it. I was doing it before that, just on the side.
That is not how functional programs work. We actually do side effects. Our program is not one big pure function, there are effects in there. That's actually the reason we write our program, is to do all those effects.
There's something missing in that definition. The definition is also bad, because when someone new to the field, someone in the industry, not in academia — my audience is more about people who are programming in the industry, meaning commercial software — when they come over, when they see that definition, they're turned off.
What does this add that the whole thing is one function? What about emails, sending emails? What about talking to another computer on the network. It's an important aspect, and to just say we don't do that as functional programmers is turning them away.
It's incorrect, but it's also turning them away. It's making it seem like it's an impractical paradigm. I want to do something about that. I want to have a good definition that I would be happy to point people to, and hopefully other people would want to point people to as well.
I look at it a lot as helping functional programming escape the ivory tower. Meaning, it's in the ivory tower. It's trapped up. There's always great knowledge. Now that it's escaping into industry, we need to start talking more practical terms. Real software is not a function from inputs to outputs. It's something much more than that.
There's a ton of ideas here. If I'm hesitating, it's because I don't know exactly how to continue. Let me take a moment here.
Why don't I just go right into the definition that I have? Functional programming is a paradigm. Meaning, it is a set of ideas, it's a set of concepts, a set of practices and almost like a theory of programming itself. It is a framework, meaning a mental framework for how to approach a problem that you're trying to solve with software.
It is a paradigm like object-oriented programming is a paradigm. Object-oriented programming is a paradigm made up of...I'll just say three things. I'll simplify it. There's three things. It's objects. Those objects have references to other objects. Objects can send messages to objects they have references to. That's very simple version of object-oriented programming.
That is, I think a good nutshell summary of object-oriented programming. Functional programming is an orthogonal view. It is a totally different view of the same problem. You can look at a problem through a totally different angle using functional programming.
When you're using object-oriented programming, what you can do is say, "Well, this object is going to do this thing and then it will solve this part of the problem, and then it will send a message to this object which will solve the next part of the problem, etc., and this one will talk to three other objects to solve the problem."
The work is done through this messages traveling around the graph of objects. Finally the work done and you have the answer. When you look at a problem in object-oriented view, you try to see, "Where are my objects? What are their messages? Where do I do this work? Where do I do that work? What object is going to do that? What message do I need to send to it to do that work?"
"What collaborators is this object going to have?" When you are solving a problem like accounting, you might say, "Well, we'll make an object as...the account will be an object. You can send it a message like deposit and it'll deposit a certain amount of money." That might be how you do it.
I'm not solving that problem right now, but that's the approach you take if you start thinking, where do I cut this problem up into little objects that talk to each other. In functional programming, we have three different concept. Those three concepts are data, calculations, and actions. Let's go over those three, and give more definition for each one.
Data is facts about events that have happened. The event might be, I measured, something on the scale, or I took a reading from the thermometer, or this is the data that the user submitted in the form. It's a fact about an event that you can use for calculation or other uses, storage, record-keeping, that kind of thing.
That's the easiest one to think about as a programmer. We use data all the time. You got your basic data like your numbers, characters, strings of characters, so you start talking about collections.
You've got any compound data like tuples of numbers or strings or letters or bytes. All this stuff is. Lists of those things. They can nest, so you can have hash maps, you can have whatever. Whatever data, all of that is data.
That's pretty easy to understand. There's more to say about data, but I want to touch on the three things first. The next thing is calculations. Calculations are...
I haven't found a good dictionary definition. I looked in several dictionaries. They all talk about calculations is the result of calculating and calculating is the act of performing calculations. It's a bad definition.
I'm just going to also do a bad definition and just say it's all about computation from inputs to outputs. Those inputs...There's actually a rule of thumb. It's probably more than a rule of thumb. It's probably a little bit stricter than that, but the idea behind the calculation is that it is timeless.
Given the same input, you will always get the same output. It doesn't matter when you run it or even if you run it. This is what we often call in functional programming in the academic world, we call it a pure function.
It is the mathematical sense of function, which is a relation between the argument to its value. Or relationship between the domain and the range, if you wish. That's a much more mathematical way of saying it.
The final one is the actions. These are often called effects or side effects, but I think that the term effect is a poor term because I'm not talking about the effect. I'm not talking about the email being sent.
I'm talking about an action, which is a representation of that email being sent, which you can then invoke that action and that causes the email to be sent. The effect is actually the cause.
If the action is the cause and the effect is what happened, action is the cause of the effect. It's not the email being sent, it is a thing that represents the email being sent. When you invoke it, it will send it.
In that way, you can manipulate it in a way that you can't manipulate an email being sent normally. You can manipulate, let's say, a Java method that will send the email. You can talk about that more abstractly. The way you tell if something is an action is it matters when you invoke it or how many times or both.
A good example is sending an email. It's a very good example. If I send zero emails, it's different from sending one email. The effect on the world is very different. It's also different if I send two emails or send three or a million emails.
All of those things are going to have much different effects on the world. It matters when I send it. If I send the email now, it's different from sending it in two weeks. I can't change that. That's part of the way the action works.
We'll get to this later, but some actions, doesn't matter when you call it, but it matters how many times you call it. Some actions, it matters when you call it, but it doesn't matter how many times you call it.
There's ways to compartmentalize these actions. Those are the three things. Data, calculations, and actions. They are the principal domains, I'm calling them. I don't know if that's a good word, but for now, domains.
That's the principle domains of functional programming and doing functional programming, programming in a functional way, means approaching a problem with these three domains in mind.
As an object-oriented programmer would look at the problem and say, "Where are my objects? What are my messages? What objects know about what other objects, etc.," a functional programmer says, "Where's the data? What data can I get from events happening in the world and how do I calculate those things? Then, what actions does the system need to take?"
There's more. There's a lot more. Hence, I'm writing a book. It's not going to be as easy [laughs] as just listing this stuff like this. The other thing about functional programming is that everything is first class.
When I say everything, there's only three types of things. There's data, calculations, and actions. The reason I'm staying away from using the term function is because in industry, it's a feature of languages.
I'm trying to avoid talking about features because functional programming is not about features, it's about this mindset. If you start talking about features, like I need functions, then you think, "Does something like Java have real functions?"
I'm trying to avoid all things that are tied up in the notion of features. You do need certain features to be able to do functional programming.
The real feature you need is that everything has to be first class. By first class, I mean, you have to be able to pass all three types of things as arguments, or return them as return values to your functions, your subroutines, or whatever feature you have.
You have to be able to get a handle on them somehow, some kind of handle on them, first class, to be able to hold them, because the principal way we write programs in functional programming is by composing these things. We'll talk about composition in a minute.
Without being able to have first-class actions and calculations...Usually, data is first class. I'm just going to ignore that. Without having to have them as first-class things in your language, you can't do functional programming.
You write a pure function. That can be a calculation. Your actions are your impure functions, all the other functions that do it. As a function, you can be first class.
You cannot pass plus to another function. You can wrap up the plus in a function that takes two-hour units, however you want to do it. It now becomes a function called "plus," P-L-U-S, spelled out, that you can use instead of the arithmetic operator plus that is not first class.
Similarly, in a language like Java, you can wrap something up as an object with a method. You can work around the parts of the language that are not first class. That lets you do functional programming in those languages.
Typically, in a functional language, all of that is taken care of for you. Everything is first class by default. You don't have to worry about that. I say "everything." All of the three types of things.
Some languages might provide something else. I'm thinking type systems. In Haskell, the types are not exactly first class. They are a different class and different level than all the values.
All your data are values. You have a bunch of types, different built-in types. You can write your own types. All that is data.
All your functions are pure by default. That really helps. You just say "functions" in Haskell. You don't have to say "pure functions."
You have your actions. That's the I/O type. There's a special type that is just for actions, which is really nice. Haskell provides just what you need for that triple separation there.
In Clojure, which is what I write in, you have the data. It provides a lot of data structures and types that you would want to use.
Clojure programmers get quite good at doing that. Clojure programmers are comfortable with that lack of compiler help on that front. It's entirely possible. My point is that it is entirely possible to not have help from the language separating out those actions from the calculations. It's certainly helpful to have that.
Sometimes, I want it. I'll tell you that.
I said I was going to talk about composition. We have these three domains. You might think at this point that it's quite obvious, that these domains obviously make sense.
I would say that that's not the case for other paradigms. People do not think in these terms in other paradigms.
In object-oriented programming, for instance, which we've already gone into, they're not thinking about whether a message pass is an effect or if it's pure or something like that. There's no easy mapping between the three.
I'm not saying it's incompatible. I'm saying that the way you cut things up...The two views cannot be reconciled with each other. If I do an object-oriented analysis, I cannot transform that into a functional programming analysis. I can't. There's no way to do that.
I can do both analyses of the same problem and have two perspectives, which will help. By having multiple perspectives, I can now see more about the problem and probably solve it better. I can't do that isomorphism. I can't do that simple transformation between the two.
They're not incompatible in that I can do both on the same problem. Both will help me solve the problem and arrive at a better solution, but one might not inform the other, because they're totally different perspectives on the same problem.
I hope that it being obvious is more a product of it being super clear. Also, I want to say, even if it is obvious and everyone already knows this, no one has written it down. I'm going to write it down in a book. That's all I have to say about that.
The next thing I want to talk about is the three domains. I feel like when you do one of these theories like this, where you try to build a coherent model, it's easy to draw some lines, some boxes, and say, "This is where everything goes."
Have you really captured something by giving a couple of rules for what goes in which bucket? I feel like it would be justified to come up with some evidence that these things make sense, that each domain is coherent in itself.
What I want to say is that you can actually stay in a domain. There are operations that will take data, two pieces of data, compose them, and give you a third piece of data.
What those two data can't do is give you an action. You could make up an action for those pieces of data, but you can stay in that domain. You can have data that turns into more data, that transforms into different data, that gets added to other data. You're just always in data. Data, data, data.
You can do the same for calculations. I can take a function. I can compose it with another function. I can do the complement of that function. I can transform it into another function.
As long as I'm just doing functions, I can stay in this arena of functions, calculations. There is a coherency to this division. It's borne out also by languages like Haskell that have this strong division. There's the data and all the data types. There is the function types, all the different...It's polymorphic, but these are functions. There is the I/O, which is the action. It's a separate thing, Haskell rules.
The same goes with action. I can take two actions. I can compose them. I can reverse them. I can run them in parallel. I can add another action. You can just stay in this domain of actions.
One rule when doing functional programming that you'll quickly learn is that you want to do as little work as possible in actions. This is the source of a lot of the bad definitions of functional program.
When I say bad, meaning, they're technically correct if you already understand functional programming and you can squint and say "Yeah. I understand what you're trying to say." He or she understands what they're trying to say. "I got it."
Avoid side effects. "OK. I see what you mean." If you can do it in a function instead of an I/O. That makes sense to a functional program but it's not a good definition and it doesn't help someone who doesn't already know...learn it.
What a lot of these bad definitions say is avoid side effects or functional programming is doing your best to describe your problem in terms of pure functions. You notice there's always this focus on the functions and they forget about the data, they forget about the actions.
Why is that? It turns out that when you diagram it, actions are the universal thing. Our machines are based on touring machine and the touring machine is actually...All it does is actions. It moves the tape back and forth. It reads and writes to the tape.
Everything is actions. The fact that you get a calculation out of the end is the mystery of computation. It's what he was trying to get at — touring was trying to get at. It's a cool thing that you can do some actions in the world and you've calculated it. You solved the problem.
It's what allows us to think and solve problems through thinking, that our brains are sending signals. All these actions are happening, these timely things. It's all timely. If the thing fired tomorrow — my brain fired tomorrow, I would have a different thought. It would be to late or it would be the wrong thought at the time and I wouldn't solve the problem that I'm solving, etc.
It's all timely so it's actions. But we get an answer out at the end which is really cool. When a procedural programmers like, "I can't program without side effects. I need it." They're right. You do need effect. We do need to send that email. We do need to write to the disc. We do need to send a message over the network. We need to do those things.
Actions are universal. We know we can represent functions in terms of actions. We also know to [inaudible 34:14] the calculus that we can represent data in terms of functions. I don't want to go in to that but with pure functions — I don't want to go [inaudible 34:25] the calculus part — you can represent any data value.
It's totally feasible. Actually, data is a subset of functions — a subset of calculations — and calculations are a subset of...running out of battery. Calculations are subset of actions but functional programmers appreciate the value of moving as much of your code down that hierarchy as possible.
As much as possible into data. If you can't get in to data try to get in to calculations and when it can't be calculation, you leave it as actions. I'm going to stop now. It's been 35 minutes. I'm getting tired. There's more to talk about. I'm getting excited because there's a cool stuff.
Subscribe because I'm going to keep talking about this. I'm going to be using this. I'm going to transcribe it and use it as part of my book. I'll probably post it to my blog and everything. Keep listening. Subscribe, like, and check out the book when it comes out.