Functional Programming: The Biggest Change Since We Killed the Goto?
Transcript
Stephen Cass: Welcome to Fixing the Future, an IEEE Spectrum podcast. I'm senior editor Stephen Cass, and this episode is brought to you by IEEE Explorer, your gateway to trusted engineering and technology research with nearly 6 million documents with research and abstracts. Today we are talking with Charles Scalfani, CTO of Panoramic Software, about how adopting functional programming could lead to cleaner and more maintainable code. Charles, welcome to Fixing the Future.
Charles Scalfani: Thank you.
Cass: So you recently wrote an expert feature for us that turned out to be incredibly popular with readers. That argued that we should be adopting this thing called functional programming. Can you briefly explain what that is?
Scalfani: Okay. Functional programming is an older version of programming, actually, than what we do today. It is basically, as it says, it's basically based around functions. So where object oriented programming is has an object model, where it's everything- you see everything through the lens of an object, and the whole world is an object, and everything in that world is an object. In functional programming, it's the similar, it's you see everything as a function, and the whole world looks like- everything in the world looks like a function. You solve all your problems with functions. The reason it's older and wasn't adopted is because the ideas were there, the mathematics, the ideas, and everything were there, the hardware just couldn't keep up with it. So it became relegated to academia and the hardware just wasn't available to do all of the things. That has been, since probably the 90s, it's been not a problem anymore.
Cass: So I just wanted to like, as somebody who is, I would call itself a kind of a very journeyman programmer. So one of the first things I learned when I'm using a new language is usually the section says, how to define a function, and there's a little- you know, everybody's got it, Python's got it, you know, even some versions of Basic used to have it, C has it. So I think function here means something different to those functions I'm used to in something like C or Python.
Scalfani: Yeah. I have a joke that I always say is that when I learned C, the first program I wrote was hello world." And when I learned Haskell, a functional programming language, the last thing I learned was hello world." And so you really, with C, you did, your first hello world" was a print function, something that printed to the console, and you could say, yay, I got my first C program working. Here it is." But the complexity of doing side effects and IO and all of that is such that it gets pushed aside for just pure functional programming. What does that look like? How do you put functions together? How do you compose them? How do you take these smaller pieces and put them all together? And the idea of side effects is something that's more advanced. And so when you get into a standard language, you just, kind of, jump in and start writing- everybody writes the hello world," thanks to Kernighan and Ritchie, what they did in their book, but you really don't get to do that for a very long time. In fact, in the book that I wrote, it isn't for hundreds of pages before you actually get to putting something on the screen. It's relegated to the fourth section of the book. So it is a difference in that. Side effects where you can affect the world is very standard in imperative languages. The languages that everybody uses C, and Java, and JavaScript, and Python and you name it, the standard languages.
And that's why it's very easy when you first learn a language is just hop in and feel like you're able to do lots of stuff, and get lots of things done very quickly. And that gets kind of deferred in a functional language. You tend to learn that later. So the kinds of functions that we deal with in functional languages were called pure functions. They're very different than how we think of functions in programming today, but more how you think of functions in math. Right? So you have inputs, you have processing that happens in the function, computations that are going to occur in that function, and then you have those outputs. And that's all. You don't get to manipulate the world in any way, shape, or form.
Cass: So I want to get back into a little bit of that tutorial on how you get started up on stuff. But it sounds to me a little bit like, I'm searching for a model, my previous model of experience. It sounds to me a little bit like kind of the Unix philosophy of piping very discrete little utility programs together, and then getting results at the end. And that kind of philosophy.
Scalfani: Yes. Yeah. That's a great example. That's like composing functions using pipes- I'm sorry, composing programs using pipes, and we compose functions in the very same way. And the power of being able to do that, the power they figured out back in Unix, to be able to just say, well, I'll write this very simple little program that just does one little thing, and then I'll just take its output and feed it into the next. And it does one little thing. And it's exactly the same thing, just at a smaller level. Because you're dealing with functions and not full programs.
Cass: Got it. But this does seem like a fairly big cultural shift where you're telling people, you don't even get to print until you're halfway through the book and so on. But I think this is something you raised in the article. We have asked programmers before to do, make fairly big shifts, and the benefits have been immense. And the one you talk about, is getting rid of goto, whereby, you know, in the beginning, we all, you know, ten, goto, whatever. And it was this goto palazza. And then we kind of realized that goto had some problems. But even though it was this very simple tool that every program are used, we've kind of mostly weaned ourselves off goto. Can you talk a little bit about sort of the parallels between saying bye bye to goto and maybe saying bye bye to some of this imperative stuff? And these things like side effects and then maybe talk a little bit about what you mean about like global state, and then- because I think that will perhaps illuminate a little bit more about what you mean about side effects.
Scalfani: When I started in programming it was way back in you know 78, 79, around that time and everything was a go- you had Basic, a machine with 8K of RAM. That was it. K. You didn't have you didn't have room to do all the fancy stuff we can do today. And so you had to try to make things as efficient as possible. And it really comes from branching down in the assembly language, right? Everybody was used to doing that, goto the, just jump over here and do this thing and then jump back maybe or return from a subroutine and you had very little machine power to do things. So goto came out of assembly language. And as it got in the higher and higher level languages, and as things got more complicated, then you wound up with what's called spaghetti code, because you can't follow the code. It's like trying to follow a strand of spaghetti in a bowl of spaghetti. And so you're like, well this is jumping to this and that's jumping to this and you don't even remember where you were anymore. And I remember looking at code like that and mostly written in assembly language.
And so as structured languages came about, people realized that if we could have this kind of branching but do it in a do it in a way in which we could abstract it. We could think about it in a more abstract level than down in the details. And so if you look at that, I use it as an example because I look to the past to try to figure out what are we doing today? If we take imperative languages and if we move to functional, we are giving up a lot of things. You can't do this and you can't do that. You don't do side effects. You don't have global state. There's all these things that you- there's no such thing as a null pointer or a null value. Those things don't exist here in this way of thinking. And it's like you have to ask yourself, wait, wait, I'm giving up these things that I'm very familiar with and well, how do you do things then in this new way? And is it beneficial or is it just a burden? So at first, it feels like a burden, an absolute burden. It's going to because you're so used to falling back on these old ways of doing things in old ways of thinking. And especially when I- I was like 36 years or 30 some odd years into programming and imperative languages, and then all of a sudden I'm thinking functionally. And now I have to change my whole mode of thinking. And you really have to say, well, is it beneficial?
So I kind of look to the past. Getting rid of the go to was highly beneficial. And I would never advocate for it back. And people did comment on the article saying, well, yeah, these languages have goto," but not the goto I'm talking about. They still have these kind of controlled gotos in C, not where you could just jump to the middle of anywhere. And that's the way things were back in the day. So, yeah, things were pretty wild back then. And we wrote much simpler bits of software. You didn't use libraries. You didn't run in operating systems always. I did a lot of embedded coding in the early days. And so you wrote everything. It was all your own code. And now, you might have written, I don't know, maybe you wrote a thousand lines of code. And now we're working in millions of lines of code. So it's a very different world, but when we came out of that early stage, we started shedding these bad habits. And we haven't done that over time. And I think you have to shed some bad habits to move to functional.
Cass: So I do want to talk really getting into the benefits of functional programming are, especially with, I think, the idea of like thinking about maintenance instead of sort of the white hot moment of creation that everybody loves to write that first draft, really thinking about how software is used. But I did just want to unpack a sentence there. And it's something that also comes from C, and it's not necessarily something that is baked into assembly in the same way, but it does come in to C, which is this idea of the null pointer. You mentioned the null. And can you talk just a little bit about the null and why it causes so much problems, not just for C, but for all of the sort of, as you call them, curly bracket languages that inherit from it.
Scalfani: Right. So in most of those languages, they all support this idea of a null. That is you don't have anything. So you either have a value or you don't have a value. And it's not- it's sort of like just this idea of that every reference to something could be potentially not- have no reference, right? You have no reference. So think of a plan of an empty bucket, right?
Cass: Just for maybe readers who are not familiar. So a pointer is something that points to a bit of memory where something of information is stored. And usually at that point, there's a valuable number. But sometimes there's just junk. And so a null pointer kind of helps you tell, ideally, what are the pointers pointing to something useful or it's pointing to to junk? Would that be kind of a fair summary or am I butchering it a little?
Scalfani: Yeah, I think at the lowest level, like if you think about C or assembly, you always have a value somewhere, right? And so what you would do is you would say, okay, so they always point to something. But if I have an address of zero at the very lowest level here, if I have an address- so if my register has a value of zero in it, and I usually use that register to dereference memory to point to someplace in memory, then just that's going to be treated specially as, oh, that's not pointing anywhere in particular. There is no value that I'm referencing. So it's a non, I have no reference. I have nothing, basically, in my hands.
Cass: So it's not something there, it's just the language is trained that if I see a zero, that's a flag, there's nothing there.
Scalfani: Right. Right. Exactly, exactly.
Cass: And then so then how does this then- so that sounds like a great idea. Wonderful. So how does this then-
Scalfani: It is.
Cass: Well, how does this cause problems later on? I've got this magic number that tells me that it's bad stuff there. Why does this thing cause problems? And then how can functional programming really help with that?
Scalfani: Okay. So the problem isn't in this idea. It's sort of a hack. It's like, oh, well, we'll just put a zero in there. And then we'll have to- so that was, okay, that solved that problem. But now you're just kicking the can. So everywhere down the road where you're dealing with this thing, now everybody has to check all the time. Right? And it's not a matter of having to check, because the situation of where you have something or you don't have something is something that's valid situation, right? So that's a perfectly valid thing. But it's when you forget to check that you get burned. And it's not built into most of the languages to where it does the checking for you and you have to say, oh, well, this thing is a null or if it's not a null, then do this you. There's all these if checks. And you just pollute your code with all the checks everywhere. Now, functional programming doesn't eliminate that. It's not magic. It doesn't eliminate it. But many of the functional languages, at least the ones that I've worked in, they have this concept of a maybe, right? So a maybe is, it can either be nothing, or it can be just something. And it's other languages call it an option. But it's the same idea. And so you either have nothing, or you just have this value. And because of that, it forces- because of the way that that's implemented, and I won't go into gory details, but because of it, they force you to the compiler won't compile if you didn't handle both cases.
And so you're forced to always handle it, as opposed to the null, you can choose to handle it or not, and you could choose to forget it, or you could go- you could not even know that it could be a null, and you could just assume you have a good value all the time. And then you don't know until you're running your program that, oh, you made a mistake. The last place you want to find out is in production when you hit a piece of code that is run rarely, but then you didn't do your null check, and then it crashes in production and you've got problems. With the maybe, you don't have a choice. You can't compile it. You can't even build your program. It really is a great tool. And many times, I still don't like the maybe. Because it's like, ugh, I have to handle maybe. Because it forces your hand. You don't have a choice. Ideally, yes, that's the right thing, but I still grumble.
Cass: I mean, I think the tendency is always to take the shortcut because you think to yourself, oh, this will never- This will never be wrong. It's fine. I mean, I just all the time. I know when I write even the limited- I know I should be checking a return value. I should be writing it so that it returns. If something goes wrong, it should return an error value, and I should be checking for that error value. But do I do that? No, I just carry on my merry way.
Scalfani: Because we know better, right? We know better.
Cass: Right. So I do want to talk a little bit about the benefits, then, that functional programming can build. And you make the case for some of these concrete benefits. And especially when it comes to maintenance. And as I say, I think, one of the charges that's fairly laid against maybe sort of the software enterprise as a whole is that it's great at creating stuff and inventing stuff, but not so good at maintaining stuff, even though there are examples we have of code, very important code that runs very important systems, that sits around for decades. So maintainability is kind of actually super important. So can you talk a little bit about those benefits, especially with regard to maintainability?
Scalfani: Yeah. So I think, so before you even get into maintainability, there's always the architectural phase, right? You want to model the problem well. So you want to have a language that can do really- can really aid you in the proper modeling of your types. And so that you can model the domain. So that's the first step, because you can write bad in any code, right? In any technology, you can destroy it. No matter how great the technology is, you can wreak havoc with it. So no technology is magical in that it's going to keep you from doing bad things. The trick about technology is that you want it to help you do good things. And encourage you and make it easy to do those good things. So that's the first step, is to have a language that's really good about modeling. And then the next thing is you want to- we haven't talked about global state, but you need to control the global state in your program. And in the early days, going back to assembly, every variable, every memory location is global, right? There is no local. The only local data you might have is if you allocated memory on a stack, or if you have registers and you pushed your old registers as you went into a subroutine, things like that. But basically everything was global.
And so we've been we've been, as languages have been progressing, we've been making things more local, what's in scope. Who has access to this variable? Who doesn't have access to the variable? And the more, if you just follow that line as you get to functional programming, you control your global state, right? And so there is no global state. You actually are passing state around all the time. So in a lot of modern, say, JavaScript, frameworks do a lot of that. They've taken a lot architecturally from functional programming, like React is one that it's a matter of how do you control your state? And that's been a problem in the browser since day one. So controlling the state is another important thing. And why am I mentioning these other things about maintainability? Because if you do these things right, if you get these things right, it aids in your maintainability, right? There's nothing that's going to fix logic problems. There's always logic, right? And if you get- if you make a logic problem mistake, there's nothing there. Like you just made the wrong call. No language is going to save you because it's got to be powerful enough so you can make those mistakes. Otherwise, you can't make all the things.
So but what it can do is it can restrict you to, you can't make this mistake, and you can't make that mistake, and you won't make this mistake. It restricts you in the mistakes, right? And it makes it easy to do the other things. And that's where the maintainability really, I think, comes in is the ability to create a system where, if you got the proper modeling of the problem, you've properly managed- because really, what are you maintaining software for? You're fixing problems, right? Or you're adding features. So that's all there really is. So if you're spending all your time fixing problems, then you don't have time to add any features. And I found that we've spent- in the old days we spent more time fixing problems than adding new features. Why? Because why are you adding features when you have bugs, right? So you have to fix the bugs first. So when we move to functional programming, I found that we were spending yeah, we still have logic problems here and there. I mean, we're still human, but most of our time was spent thinking about new features. Like we would put something into production, you got to have good QA, no matter how great the language is. But if you have good QA and you do your job right, and you have a good solid language that helps you architect it originally correct, then you don't think about like, oh, I have all these bugs all the time, or these crashes in production. You just don't have crashes in production. Most of that stuff's caught before that. The language doesn't let you paint yourself into a corner.
So there's a lot of those kinds of things. So you're like, oh, well, what can I add? Oh, let's add this new feature. And that's really value add, at the business level, because that's really at the end of the day, it doesn't matter how cool some technology is. But if it doesn't really have a bottom line return on investment, there's no sense in doing it. Unless it's a hobby, but for most of us, it's a job, and it matters the bottom line of the business. And the bottom line of the business is you want to make improvements to your product so you can get either greater market share, keep your customers happy and keep them from moving to people who can add features to their products. Competitors and so forth. So I think the maintainability part comes with, originally with really good implementation, initial implementation.
Cass: So I want to get that idea of implementations. So oftentimes, when I think about- maybe I'm in the past, I've thought about functional languages. And I have thought about them in this kind of academic way, or else things that live in deep black boxes way down in the system. But you have been working on PureScript, which is something that is directly applicable to web browsers, which is, when I think about advanced clever mathematical code models, browsers are not necessarily what I would associate. That's kind of a very fast and loose environment, historically. So can you talk a little bit about PureScript and how people can kind of get a little bit of experience in that?
Scalfani: PureScript is a statically typed, purely functional language that has its lineage from Haskell, which would start as an academic language. And it compiles into JavaScript so that it can run in the browser, but it also can run on the back end, running in Node. Or you can write it and have your program run in Electron, which is like a desktop application. So pretty much everywhere JavaScript works, you can pretty much get PureScript to work. I've done it in backends, and I've done it in browsers. I haven't done it in Electron yet, but it's pretty academic. So that's totally doable. I know other people have done it. So it doesn't get more run of the mill, kind of, programming than the browser, right? And JavaScript is a pretty terrible language, honestly. It's terrible on so many ways because you can shoot your foot off in so many different ways in JavaScript. And every time I have to write a little bit of JavaScript, just the tiniest bit of JavaScript, I'm always getting burned constantly.
And so anyway, so what is a pure functional language? A pure functional language is that all your functions are pure, and a pure function is what I talked about earlier. It only has access to the inputs to a function, it does its computations, and it has its outputs. So that's kind of like what we did in math, right? You have a function, f of x, x gets some value, and maybe your function is x+2, and so it takes the x, it adds two to it, and the result is whatever that value is, right? Whatever the computation is. So that's what it purely functional language is. It's completely pure. And there are languages that are hybrids, right? PureScript, Haskell, Elm. These are all languages that are pure. And they don't compromise. So compromised languages are really great in the beginning, but you can easily lose out on all the benefits, right? So if you can- it's the same thing with the goto, right? If we had, if we relegated goto to, like, okay, we're going to stick it in this corner and you sort of don't want to use it. It doesn't stop you from pulling that off the shelf and using it all day, right? So it's best to just eliminate something and not compromise. Not have a compromise language. To me, Scala as a compromise language. It's not fully functional. And there are lots, like Clojure, I believe, has- even JavaScript. JavaScript is actually, for me, was my introduction to functional programming. There's functional concepts in JavaScript.
And I thought JavaScript was the best thing since sliced bread when I had those things. I didn't know they were functional at the time, but I'm like, this is something that I've been looking for for years, and I finally have it in this language called JavaScript, and I can pass a function as a parameter. I mean, I wanted that for decades. And all of a sudden, I could do it. And so I'm a big proponent of a purely functional languages because of that. Because of hybrids don't work well. And all you need is a single library that you're using that didn't- the author didn't use all the benefits, and all of a sudden, now your whole thing is messed up. Whatever you've built is tainted by this library that isn't that isn't pure, let's say. So I think that the benefits of Haskell and PureScript being fully pure are really great. Complications are, you have to think very differently because of that, because we're not used to thinking that way. There's all these extra things that have to be built that are all part of the libraries that make that much, much easier. But then you have to understand the concepts. So I hope that explains PureScript a little bit.
Cass: Well, I literally could go back and forth with you all day because this really is truly fascinating, but I'm afraid we're out of time. So I do very much want to thank you for talking with us today.
Scalfani: Great. Thank you. It was fun.
Cass: Yeah. I really was. So today in Fixing the Future, we were talking with Charles Scalfani about functional programming and creating better code. I'm Stephen Cass of IEEE Spectrum, and I hope you'll join us next time.