Is Haskell the best procedural language?
Functional programming is a mindset that distinguishes actions, calculations, and data. That's where it derives its power. Simply applying the discipline of 'only pure functions' lets you programming using a procedural mindset and still think you're doing functional programming.
Is Haskell the best procedural language? Hello, my name is Eric Normand. This is my podcast. Before we get into the topic, I want to thank you for being on this exploratory journey with me. Sometimes, I don't know where I'm going with these topics and I hope that's clear that these are ideas that I'm trying to explore.
I love discussing them. I hope that we can all approach these things in a spirit of adventure. Also, before we get into the topic, I want to talk about my new book, "Grokking Simplicity." It is out now in print. You can buy it on manning.com. You can also get it on Amazon. Maybe by the time you listen to this, it's already available. If it's not, you can preorder it and you'll get it as soon as it is available.
They say May 18th for when it will be available. The book is called "Grokking Simplicity." It's the book that I wanted to recommend to people who asked me, "Why functional programming? What is functional programming? How do I get into it?"
There were a lot of books out there, but none that were oriented toward a beginner who hadn't started their functional journey. There's a lot of books out there that maybe if you had 10 or 15 years of experience in programming, you could work your way through. Nothing for someone with a couple of years of experience.
It starts very basic. We talk about pure functions. I call them calculations in the book. We talk about those for about eight chapters, whereas, I found that most books would put a sentence or two defining it and then move on to more complicated stuff.
I don't know about you, but I feel like that's where the meat of the benefit of functional programming is. The majority of benefit is in being able to recognize and work with pure functions. There's more benefit in first-class functions but that's in part two.
This is a two-part book, approximately half is about dealing with pure and impure functions, calculations, and actions, as I call them. Second part is about first-class functions. All right, please buy the book. Thank you, if you already have.
The topic today is about Haskell as a procedural language, and is it the best procedural language? I have to admit, it's a provocative title meant to provoke and pique your curiosity. I'm going to tell a story to explain what I mean.
I worked in Haskell for about three years at a company. One day the CTO was fretting that the software was getting so big, the number of lines of code. He was wondering, "What's going on? Is our problem domain that complicated that it requires so much code? Is it that complex?"
I agreed with him that it was getting big, but my comment was, "Well, we're not doing much functional programming." He said, "What do you mean we're not doing functional programming? It's Haskell. What else could it be?"
I didn't have an answer at the time, but I have an answer now. I've thought a lot about it. It has to do with Haskell's I/O and do notation, and the general attitude around Haskell. This isn't about Haskell.
I'm not trying to dunk on Haskell or anything, but it's a disagreement I have with a certain attitude that is common in the functional programming circles. Haskell has this thing called the I/O type, which represents all sorts of different operations you can do on the outside world. Actions that you can take.
You can read from the file system, you can send a Web request, you can get user input, all this stuff is under the I/O type.
To make it easier to use, and to be able to write more procedural code, there is a syntactic sugar for Haskell's I/O type, and it works for other monads as well. It's called the Do Notation. It's syntactic sugar, it's simple how it takes these statements, these lines, and converts them into monadic bind operations.
It makes it easy to write these kinds of procedural code in it, but it's not procedural in the way that, say something like Python is, where it's not executing the lines, the statements, one at a time. It is composing them up into a big value in the I/O type.
Then it's returning from the main function, that big value, the return type of main is I/O, and it returns this big value. The runtime takes that, this computed I/O value, and it executes it. It evaluates it and does all this stuff. It does the email sending and the reading from the files and everything.
What's happened is all the impure stuff has been deferred to the runtime. That leaves your code very pure. This is a way to make all your code in Haskell pure, even though you do need to do actions. Your software does need to do stuff.
The problem that I have with this, it's a cool system. It's neat. It's very interesting how they did this. In other languages, they often call this an effect system. There are different effects systems for Haskell as well, besides the built in I/O one. It's called an effect system, because it lets you compose effects actions up, and then have some system that executes them.
The reason I disagree with this, disagree is a weird word, I won't qualify it like that, I'll just say that, to me, functional programming is a mindset.
In the story, I was telling about our code not being functional programming, even though it was in Haskell, the problem was we weren't doing the stuff I consider the most powerful stuff in functional programming, which was making higher-order abstractions, making data abstractions, and operating on that data, representing something as a pure domain model.
We weren't doing that. We were writing what I consider very procedural code like e-receive or Web request. This is all in I/O. You receive a Web request, you validate it, you send a command to the database, another action, then you get a response, or you convert that to a response, and then you send it off.
All these things were like step-by-step. If you did an analysis of the code, you would see that a good portion of our code was an I/O. It was procedural code. What I thought we should be doing, if we were going to go to more FP, is building, you need the I/O, fine. That's normal.
More of our code should be pushed toward building up a domain representation of the current state of the system, and then being able to operate on that state in a pure way, in a functional way. Then there will be a minimal layer of I/O stuff.
Just as an example, you could fetch from the database, that's one I/O thing. Now you do this big computation, what should happen next based on what's in the database, you generate this big domain or representation of the current state of that entity. Then you do some pure transformation of it, then you store it back in the database.
The layer that does the imperative stuff is very small. It reads, does a pure thing, some functional thing, and then stores it back in the database. Something like that. I'm making a caricature of it, but that was my idea.
The hope would be that, by doing it that way, instead of a procedural way, you would gain a lot, it would take less code, let's put it like that. You would be able to be more expressive with it.
The difference that I see is that in the mindset, you don't want to be writing stuff in I/O. Even though, yes, in I/O, it's still pure, because it's Haskell. It's procedural and you're thinking a different way. You want less stuff in I/O and more stuff in functions that don't return I/O.
I'm going to call it a disagreement now, that I don't think that because you're doing stuff in an effects system where even your procedural code happens to be pure, I don't think you're doing functional programming.
You're doing procedural programming with the crazy indirection that something else is going to execute the code. You haven't translated that into your thought process and how you're designing the system, how you're creating the abstractions and things that encode meaning in your program.
I'm not making a judgment call. This is more of a meaning argument. I'm arguing that functional programming is, because it's a paradigm, it is about how you think. Just because your language makes everything pure, does not mean you're thinking that way. You're thinking in the FP way.
I should say I'm not picking on Haskell. The I/O type is brilliant in that it does cut the world on the right line. If you had to pick a line where this stuff is pure and this stuff is impure, the I/O line right there is nice. You have this type that represents all the actions. That's nice because you can be sure that all the other stuff doesn't have any actions in it.
Now, you have the type system on your side that can analyze a huge amount of code and tell you, "Yes, these are all the actions." You still want to shrink those actions down. You still want to make sure that they're essential, so that you can have more of your logic and your domain and the meaning that you're encoding being done in the calculation.
I'll say it again in a different way. FP is a mindset that, to my mind, I would say the mindset is separating out, distinguishing, certainly in your mind. You could have help of the compiler to help you figure out what's what. You're distinguishing between actions, calculations, and data.
You're going to program with actions, you need to. Your software wouldn't do anything useful if it didn't. You want to figure those out, because those are the hardest to deal with, what are the actions, and you want to only have the actions that you need. The other stuff, you want to push into calculations.
A common way to define FP is not as a paradigm, but more like as a discipline where you're only programming with pure functions, the constraint that you're putting on yourself and on your software, that it's only pure stuff.
When you look at it that way, everything you do in Haskell is necessarily FP, even if everything is in the do notation. You haven't got any improvement in how you're modeling your system. It's still a procedural paradigm. In terms of paradigm as a mindset, paradigm as a set of concepts that help you solve a problem. You're still in that procedural mindset. Even if everything is pure, you're deferring the actions to your runtime.
Doing that gives you some advantages, but it's not it. That is not functional programming, as I see it. It's about the mindset. It's about creating these higher-order abstractions that the pure functions allow you to do. It allows you to manipulate stuff in interesting ways.
I think I've explained myself well. This is an argument about meaning. Functional programming is a mindset. It's not a language feature, and it's not a discipline. Although the discipline can help, it is about this distinction.
Distinction is actions, calculations, and data, the distinction between those three things. It's a distinction that other paradigms don't make, which is good evidence that it is essential to functional programming.
Another thing is if you asked a functional programmer, if you asked them what's the definition of functional programming they probably won't say that.
If you ask them, is it important to functional programming, to be able to distinguish between actions, calculations, and data they will say, "Yes, it is important, it is vital, it is essential?" You couldn't do FP without that.
You can try that yourself, I've done that, I've gone and asked them to confirm that, and I've never had someone say, "No, that's not important." To me, if it's a distinction that we make, functional programmers make, that other paradigms don't make. Everyone who practices functional programming agrees that it's essential. [laughs]
To me, that's like, that's the definition then. That's what distinguishes us and you can't separate it from it. It seems like a definition to me.
I want to thank you again for being on this journey with me, I'm exploring these ideas. I'm often talking out loud. My name is Eric Normand. This has been a thought on functional programming. Thank you very much. As always, rock on.