Lucas Dohmen: Welcome, listeners, to a new episode of the CaSE Podcast. In our conversation today I have Eric Normand here. He's the person behind LispCast and Purely Functional TV. He's a consultant and he's the author of Grokking Simplicity. Welcome to the show, Eric.
Eric Normand: Thank you. I'm happy to be here.
Lucas Dohmen: I think you're pretty well known for being a functional programmer in a lot of different languages... What did get you into functional programming, and what's keeping you there?
Eric Normand: Good question. So I started learning functional programming a long time ago, when I was still in college. I took a course on artificial intelligence... This was old school artificial intelligence, before the machine learning revolution happened. This is in late '99, 2000, something like that. So it was very old school, it was in Lisp, and we had to learn Common Lisp for the class.
Eric Normand: At first, I was very skeptical. It felt very awkward. But by the end of the class, I felt like there was so much more there. The stuff we had written was so much more powerful and easy to write than Java, which is what all the other classes were in. I don't think I could have done it in Java, those assignments.
Eric Normand: The assignments were stuff like make a simple logic reasoner. So you would give it some propositions, just propositional logic, "A and B, and not C." And you'd just have a list of these. Then you would say whether that conclusion follows logically from that set of premises. You can do that pretty mechanically, but it basically involved manipulating this big, long list of all the possible things that you can deduce from those initial premises, and just keep expanding it and expanding it and reducing it, normalizing the forms of each one so that you can compare "Oh, I already know that, I already know this." So you keep your lists small, and you can just keep growing it... And eventually, you might hit the conclusion. And then you'd know whether it was true.
Eric Normand: So that kind of thing would have been really hard to do in Java. You would just have to write so much stuff yourself... Or after you've learned how to write it in Lisp, you might go back and say "I'm just gonna do it using a HashMap, and strings..." You know, just basic stuff, and not try to model it in classes.
Eric Normand: So anyway I got interested in it by that, and then I had some time -- I was in college, I had a lot of free time... And one thing I got interested in was writing a game. So I looked around and I read something -- I think it was by Richard Stallman, the author of Emacs. It was an interview, and someone had asked him "Why is it written in Lisp?" And he said "Well, Lisp is something cool, because you can write one in a weekend." And I was like "Wait, I can write a Lips in a weekend and then build my game in that?", instead of writing in C, which might be fun, but you know... It seemed like a cool challenge.
Eric Normand: So I wrote a Lisp in C... It took more than a weekend. I could probably do it in a weekend now that I've done it before, but the first time took a while. And it had a garbage collector, and an evaluator, an interpreter... And it worked. You know, it did basic stuff. I never got around to writing the game, I just got fascinated by this Lisp.
Eric Normand: Anyway, all these things sent me down this road... You know, combined with my natural contrarianism, like wanting to do things a different way from what everyone else was doing. Everyone else in my school was using Java. I started doing my class assignments in Lisp, and found that I was doing them more easily than the people doing Java... That's where it all started. I had to go down the road of basically learning object-oriented programming better than I had, so that I could unlearn it... Like, "What am I actually doing here? Why are these classes here?" Just really introspecting and researching "What is going on here?" and realizing where it's good and where it's not so good, and uprooting the hold it had on me where I would just do that by default... And get more and more into the functional stuff.
Lucas Dohmen: Nice. Okay. So you already said you are a little bit of a contrarian... I noticed when reading your book that sometimes you use different words for concepts that I know from different fields, and one of them is that you are talking about functional thinking, instead of functional programming. What's the difference between those words? Or are they the same word?
Eric Normand: That's a really good question, and it actually has a really complex answer. I'll just go ahead and explain it. So I wanted my book -- this is when I first started thinking "I'm going to write a book." I wanted the book to be called Functional Programming. Full stop, no subtitle or anything. I just wanted this to be THE book on functional programming. And I've written a lot of blog posts and done a lot of SEO, and you know, you just write your post, the title of what it is, right? You just want to explain to people what it's about. But the publisher had a different idea. They actually thought that using functional programming in the title is bad for sales. People don't want to read a book on functional programming.
Eric Normand: We disagreed, and I think Functional Thinking was the compromise. His idea was "We're not gonna tell them it's about functional programming, but it's about how to think like a functional programmer, so it's gonna be called Functional Thinking."
Eric Normand: I actually do like the term "functional thinking" though. It's focusing more in the concepts and mental skills that are necessary when doing functional programming, and that you're approaching a problem from a different way, instead of what a lot of functional programming books are about, which is more about what the code looks like, and what techniques, or how to use some functional library, or something like that. That's not really what the book is about.
Lucas Dohmen: Okay. But still, the title of the book is Grokking Simplicity, so what's the connection to simplicity? Why is there "Simplicity" in the title, and not "Functional"?
Eric Normand: Okay. Again, a complex answer... So I wrote several drafts of the first couple of chapters - first three chapters, really - before landing on the format that it's in now. One format which actually got really far in polish, in one of the drafts, was -- the introduction was going to kind of justify functional programming, like "What is the reason it exists?" So it was an analysis of complexity in software. Why does software get complex?
Eric Normand: Factorial growth is really big, so that means that you have to know, as a programmer, that all possible paths - because any one of them could happen when a user does something. Any one of them, any one of these factorial -- you have 12 steps in a process, and two of them running at the same time. It's already a million different ways that they can run. So this is what the chapter was about - you've got a million different ways that they can run, and your job as a programmer is to guarantee that all of them are correct. If you put a 13th one, it's at like seven million, because it's factorial. It explodes really fast.
Eric Normand: So this is hard... The complexity is more like algorithmic complexity, where you're kind of analyzing not -- it's something measurable, like "How many steps does this take as something grows?" So that was one source of complexity.
Eric Normand: The other source of complexity was with branching. So if you have an if statement, that implies two branches. One where if it's true, the test is true, and one where the test is false. And if you have two in a row, they multiply. So you have four. You have three in a row, that's eight different ways that something can happen. You have eight different paths through these three branches. So it's multiplying. It's like an exponential increase in the number of branches. So the question is "Are all of them correct?", if any of those branches can happen. Every time you add a conditional, you're potentially doubling the number of paths. We wanna reduce the number of conditions.
Lucas Dohmen: Yes.
Eric Normand: And one source of conditionals is having a poorly-fit data model. This was my attempt at solving the problem. It's like, you're gonna need conditionals. Some fields, some domains are really complex. So my solution was to have a better-fitting data model. Because every time you have a corner case, you need a branch to determine whether you're in that corner case. This was my idea, and I was gonna spend a lot of time talking about how to do a better-fit data model.
Eric Normand: Both of these ideas - this idea of managing the complexity of the interleaving, so the number of orderings of different timelines. And data modeling. I think that they come from functional programming. You don't see this that much in, say, object-oriented programming. And that was my initial idea. I think the book would have been very boring if I had actually published that draft... But it was good for me to have an underlying understanding of what the real problem that I was solving was and proposing a solution to it.
Eric Normand: I eventually came up with a different format, that I think is better at teaching... It's a lot less theoretical, it's more like "Let's get into the code." So, I don't actually talk about those things so much, that complexity. I think the problem is the disconnect is there. It’s not obvious that it's about complexity and it's opposite simplicity, because I don't talk about it so much when I'm actually teaching the thing.
Eric Normand: So it is a good question, and it's got a historical reason. The title was chosen by the publisher... Because that's their job, right? They do the cover, and the title... That's the marketing stuff. And the publisher had read this other draft and hadn't read the new draft, so didn't know that it was a totally different kind of book.
Lucas Dohmen: Interesting.
Eric Normand: And you know, he's busy... I mean, I didn't know that at the time, that he hadn't read it, but he proposed this title, and I don't think it's a wrong title, or anything...
Lucas Dohmen: No, I didn't mean that...
Eric Normand: I wasn't implying that. It wasn't the title I wanted, which was "Functional Programming."
Lucas Dohmen: Interesting. So much in the title already. So one thing that I've found interesting - in the foreword, Guy Steele talks about organizing side effects as like the main thing in functional programming. But in your book, you call it implicit and explicit inputs and outputs. Why did you choose those words, and what do you mean with those, for those people who don't know what side effects are?
Eric Normand: Okay, so side effect is a term used in functional programming to talk about stuff that happens when you call a function that is separate from the arguments and the return value. So the idea is that a function is supposed to be like a mathematical function; this is the idea that functional programming -- this is where the term comes from. You call a function to get its return value. And anything else that happens is kind of the side effect that you have to worry about. Just like when you take a medicine -- you take the Ibuprofen to get rid of your headache, but maybe it makes you drowsy, or whatever is the side effect. So your function might have the side effect of mutating a global variable. Or it might have a side effect of sending an email. Okay, so that's the idea of side effect.
Eric Normand: In my book, I wanted to be a bit more clear that there are side effects that -- I'm going to cut it in two. So there are some side effects that are getting information into the function. So you might be reading from the database. And there are some side effects that are sending data out of the function. So you might be sending an email, or writing to a file, or something... Because they have to be mitigated in different ways. So an implicit input - you wanna make that into an explicit input. And the explicit inputs of a function are its arguments. So instead of reading from the database in the function, you read from the database outside of it and pass in the value you get from the database as an argument.
Eric Normand: Likewise, instead of writing to the file, you would return the string or whatever that you would have written to the file, and then the thing that called you would save it and then write that to the file. So that's a way of explicitly laying out the process of this refactoring, of making something into a function without side effects, which I call it calculation in the book.
Lucas Dohmen: Okay, so in the book you say that there are calculation and actions, but the third actor in this play is data. So what is data? You also talk about events... Is all data events, or what's the connection there?
Eric Normand: Right. So if you look up data in the dictionary, which I feel like you have to do if you're writing a book and you're gonna use a term like that... One of the definitions is "Facts about events." And I think that that really captures the essence of data in a computer program in software engineering. We're building information systems, information is coming into the system, we're doing something with it, and then we're sending more information out, maybe in a different form or something.
Eric Normand: Your question is "Is all data events?" and I think the answer is yes. Let me explain that because a lot of people ask that. They're like "Well, what if it's not an event?" Like, "I have data about a person." So that's not an event, that's an entity in the world, or it's an object... It's not an event. But where did that data come from? Maybe it was from someone submitting a form on a website, and that receipt of that request, that form post is an event. All you know is someone filled out this field and pressed Submit, and you got that. You don't know if that actually corresponds to somebody's real name. That's just this thing that they posted. That's all you know. So, it is an event. We often forget that, so I wanted a definition that highlighted that it's all events. And we make assumptions - we have to - that "Well, the data I get is garbage in, garbage out. If you give me bad data, I can't figure out what's the truth..." But it's all events. We do transformations, and we make assumptions so that we can interpret that data (or those events), but it's all just facts about the events.
Eric Normand: You could have an event, like "Well, I read the thermometer at this time, and it told me this." That's all you know. You just have some sensor data, it's a fact about what happened at that time. It doesn't mean anything about you have a true idea, a true understanding of the actual temperature. It just means that that's what the sensor sent you. The sensor could be faulty, they could have been at a weird time, where a drop of water fell on it... It's all sorts of problems. So you have to remember that these are just moments in time, and facts about what happened.
Lucas Dohmen: Okay, cool. So we have our calculations, our actions and our events... One of the examples in the book that I really liked is from real life - I want to go grocery shopping, and I want to know what I need, so I go to my fridge, then I walk to the store, I buy something, I go back, right? If I describe it like that, then all of those things are basically actions, right? They all have side effects. So how does functional thinking help me to find calculations or maybe events in this procedure?
Eric Normand: Right. So, what I think happens is that the stuff that I'm calling calculations, all these functions without side effects - they're happening, but they're all in a person's head. So, they're invisible. The driving to the store, putting stuff in your shopping cart - all that stuff you can observe someone do. What you can't observe them do is think "Well, I have orange juice and milk and tomatoes, but I need orange juice, milk, tomatoes, carrots, onions etc. What do I need to buy?" They make this calculation in their head. It happens, and it might even be instantaneous; they don't even realize. They're not doing it consciously. But when you sit down to think about how to program it, obviously something needs to calculate what to buy based on what you have and what you need.
Eric Normand: So the calculations are often -- if you're using this kind of real-world process that you're trying to break down, the calculations are often thought processes. And what's interesting about thought processes is just thinking about what you need does not affect the world. So that shows you that the calculations can be run -- you can run them twice. You can do the same calculation twice; you should get the same answer, and it's not going to change anything, so it's safe to run them twice as well.
Eric Normand: The question about "Where do you find the data?" The data is often the result of these calculations. There's like kind of in between data. Also, actions can generate data. So you look in the fridge and you see what you have... Looking in the fridge is an action because if you do it at a different time or on a different day, you'll have different stuff in your fridge. The fridge is changing all the time. And so it depends on when you do it, so it's an action. But it generates a list -- you know, if you were gonna make a data structure out of it, it would be a list of what you have in your fridge. And you have a list - maybe it's on a piece of paper, or in your head, but it's data of what you need. "These are the things I want to have. This is what I have", and now these are the things that you need to buy, which is like the difference. A functional programmer gets good at seeing those invisible calculations, and the sort of transient data that is the inputs and the outputs of those calculations.
Lucas Dohmen: I think this is a good example, because if we look at a fridge, then another person, which would be a process in programming probably, could go to the fridge and get in, eat a piece of cheese, and close the fridge again while you were shopping, right? So the content of the fridge could change since the last time you looked at it.
Lucas Dohmen: So when I talk to people that are really into functional programming, apart from side effects, the other thing that they always talk about is immutability. So how can I do something like modeling a fridge immutable? Because I need to have side effects, I need to have immutability, otherwise my programs wouldn't do anything, right?
Eric Normand: Right, right. That's a really good question. This is one of those -- we were talking about me being a little contrarian... This is where I think my contrarianism generated a little insight, compared to the typical view of functional programming. I see, as a professional functional programmer, what I do and what other people that I work with do - we do use mutable things all the time. So we do have mutable state. We recognize that we need it... So we would represent the fridge as a mutable thing. A mutable variable, or something like that. The difference though is that we recognize that when I look in that fridge at time X, and I make a list of what's in there, when I go to the store, that list cannot change. That is what I saw at that time. If my friend, my roommate eats my cheese while I'm at the store, that shouldn't change my current list. That would get very confusing very quickly. This is what the whole problem of concurrency is - because now I'm gonna come back home and there's no cheese; and I was just at the store, I could have bought it.
Eric Normand: So you have this problem no matter what. There's no way to solve it. They could text you "I just ate the cheese." Well, where are you on your shopping trip? Are you checking out? Are you on the way home already?" There's no way to solve it. You're gonna have to go to the store again, sometimes. Maybe they catch you at the right time, you're in the cheese department, and they text you right at that time, and you're like "Oh, that was nice. That happened really smoothly." But notice, this is another piece of data sent to you. This is a fact. "We're out of cheese." So you can modify your list based on this new fact coming in. So I just wanna make that clear. Nothing is mutating. You still remember "Well, I had this list that included cheese -- I thought I had cheese. At this time I did have cheese. Now I'm making a new list that's subtracting cheese from that old list I had."
Eric Normand: It sounds really complicated, of course, when you describe all this stuff happening in a computer's memory... It can get really complicated. And the question was about using mutable things. So what I think is the insight that I try to bake into this book is that functional programmers do use mutable state, and we have a lot of tricks to make it easier to work with, that people programming in other paradigms don't use. So I'm trying to kind of flip it, where -- people typically say functional programming is all about "No side effects. Everything is immutable", and I'm saying "No, it’s actually the opposite." The "No side effects and everything is immutable" - that makes programming really easy; that stuff is so simple... You can just put stuff in there and never worry about it. You test it once, it works, it's never gonna change, it's fine.
Eric Normand: Now let's focus on the hard stuff, which is that you're sharing a fridge with your friend. You can both change it at the same time -- I mean, not at the same time, but without each other knowing that you're changing it. You have this mutable thing that's shared. How do you share it efficiently and productively, so that you're not stepping all over each other, you're not -- you see what I'm saying? So you might have what we call concurrency primitives. Sharing a fridge is different; let's talk about sharing a bathroom. The typical way you would share a bathroom is you have a lock. If I'm in there, I'm gonna put the lock, and now no one else can come in while I'm in there. So it's like a one at a time kind of situation... And usually, that works well enough. If you've got two people sharing a bathroom, if someone's in there, you just come back later, right? You just try again. That probably wouldn't work so well if you have ten people sharing the bathroom...
Lucas Dohmen: Yeah.
Eric Normand: ...because there's gonna be now like "Well, I came back and it was busy with someone else." So now you wanna wait there, and just stand there, and now you're wasting your time. There's all this contention for this shared resource... So you'd probably have to come up with a better concurrency primitive, which might be something like a queue... Like, "Okay, I'm gonna put a token, my name, a little piece of cardboard with my name on it, in line." There's some line next to the bathroom. So when the person in there comes out, they're going to call the next person on the list, the next person in that queue, and say "Hey, it's your turn." Then they'll come in and move their thing out of the queue.
Eric Normand: So you have a system that is somehow more fair, and it's gonna work for this larger contention. It's a larger amount of contention. Maybe if you had more people sharing that bathroom, you need something different, like a schedule. Like, "Hey, you get to use the bathroom" - especially in the morning, when everyone's getting ready. "You get the bathroom from this time to this time." If you miss it, you miss it. But it's fair, and you can kind of work out, "Well, I need it earlier, because I go to work earlier", or whatever. You can work it out. That might be a better system for those times.
Eric Normand: So these are concurrency primitives, and you can code these up, you can program these, and what it turns out that they are is -- I mean, how I see them is you're building a new model of time. You're building a new semantics for how time works for this bathroom. Time in a programming language - it has the semantics, and you have to build on what the programming language gives you, to build a new model of time that better models the way you need it to run... Because by default, it's unlikely that all this stuff that you need to program happens the right way in your language.
Lucas Dohmen: That's interesting, because one of the principles you also talk about is defensive copying. Is that in this category, or is that something different?
Eric Normand: Defensive copying is a tool, it's a technique that works well -- so if you're trying to maintain an immutable discipline, but you have to share data with a system that doesn't implement the same discipline, or might not have any discipline... We often talk about legacy code, which is code you can't change right now - you don't have the time to change - and it's often written with older practices or whatever... So you wanna pass it a HashMap of data, or an array of data, and it's going to modify it. Or maybe you don't know - maybe it will modify it, maybe it won't. You don't trust it. So what are you gonna do? You have to copy the whole thing and send it the copy. It's the same data, it's the same facts, but you're gonna give them a copy that they can do whatever they need to do with. If they're gonna add and remove stuff from the list, that's fine. You have your original with you, and you kind of lose the pointer to the copy because you don't wanna accidentally use it later, because you don't know how it's been changed.
Lucas Dohmen: Okay, cool. One thing that is currently keeping me thinking about different things - one of the ideas you also mention in the book is the idea about facts being immutable, and that you look at the facts and then you can see a current state from the facts if you look at them in order. And there are different architectures, like the Kappa architecture, which put that quite far, and make it like a core principle of the entire thing. But especially -- like, I live in Germany, and GDPR, all the other rules and laws that we have here, they say that we have the right to change things; we can delete our data.
Eric Normand: The right to be forgotten.
Lucas Dohmen: Yes, exactly. If I have the right to be forgotten, then facts can be changed. They need to be deleted. Do you think that there's a problem, and does it make those principles not apply in those scenarios, or is there a way around it?
Eric Normand: I think there's a way around it. There's a lot of angles to this...But one approach that I've seen, which happens in the Datomic database, which is an immutable database; it's append-only, so it remembers the whole past, the whole history... And then if you want to make a change (let's call it that), it adds a new row.
Eric Normand: So if you wanna change your name, you had a mistake in your name, it was misspelled, you wanna say "Wait, my name is actually with a C, not a CK" or whatever - so you make a new row that says "Today, this user said his name was Eric." So boom, now I know; okay, from now on I'll count you as Eric.
Eric Normand: What you're asking is "Well, I want them to forget my name." So what the solution is in Datomic is it leaves the row, but it deletes just the name. So I still have the data, I still have the record that he changed his name, but I don't know what it was. And you delete all of them, all the records about my name. You don't delete the record, you just delete the field; you just blank it out, like "Oh, he changed his name here, but I don't know what he changed it to." So the data is gone from the database. The actual personal information is gone, but the structure of the database is still intact, and it still preserves all that other information that you were trying to preserve, which was the fact that things were changed.
Eric Normand: I haven't done an analysis to see if that actually implements the laws of GDPR, but it seems to me like a good first step for how you would do something like that. I guess that's my answer.
Eric Normand: Basically, if I had chosen Haskell - well, immutability is baked in, pure functions are baked in... All this stuff is just there, and it's great, it's convenient; if you are a functional programmer, that's what you want - you want it to just be default, as it makes it easier for the programmer. So it's better to program in Haskell if you wanna do functional programming, but it's not as good for learning, and certainly not as good for me teaching... Because I would have to show you how to build Haskell, which would be really hard.
Eric Normand: You're also asking whether there's a lot of stuff from OO that would also be applicable, and I think the answer is yes. I think there are a lot of great things in OO that we use as functional programmers. We kind of live in an OO world, meaning the dominant programming paradigm in most places, most languages, is object-oriented... And it's very useful to have the kind of open polymorphism that OO has. Those things are very cool. And one of my goals was to show that the ideas of FP are applicable immediately. If you're having some trouble, some code is buggy, you can start refactoring it in these ways that FP programmers think, and it'll start improving the quality of the code.
Eric Normand: You don't have to go all-in, like "Okay, we've gotta rewrite in a functional way." I wanted it to be like "Let's start with some typical--" I mean, we start with procedural code in the book. This is like standard procedural, doesn't look like anything fishy, but from a functional perspective, there's a lot to improve. And yeah, we start from there.
Eric Normand: The idea of information hiding I think is valuable. We talk about something very similar, which is called an abstraction barrier. That's sort of how we talk about it in functional programming. And the idea is there's a lot to keep in your head when you're programming in a system. You have to understand what data structure is that, and how does the API for that work, and what methods are available for this API, and that thing... So you have a lot to keep in your head, and you want some way of kind of cutting it up so that at some point you don't have to understand the rest. You can just say "Okay, I understand that when I have this object, I have these seven methods on it, and I can call those, and I don't have to care how it works on the inside. I just trust that it works." And that's an abstraction barrier. I can ignore the details past those methods that I call.
Eric Normand: So we do that in functional programming - we create a barrier where we say "Look, if you just stick to this interface, these functions, you don't have to worry about how it works." You don't have to worry about what data structure I'm using to implement it, you don't have to worry about all the details. You just stick to these functions in the interface, and you're good.
Eric Normand: So in the book we use it as a way of -- you know, practically it means two teams don't have to communicate as much. It removes the dependency between the teams because they negotiated this interface, and it's like a contract. "We're gonna make the functions you need, and you don't have to worry about how they work. And you just use those functions and we'll trust that you won't mess with anything else. You'll just call those functions. And we don't have to talk, except maybe to renegotiate if you need a new function, or whatever."
Eric Normand: So the marketing team and the programming team can now coordinate, because the marketing team has to write custom sales routines, but they don't need to know how the shopping cart works; they just have this interface for it.
Eric Normand: The other thing is it means that concurrency is a lot easier, because immutability makes concurrency easier. It makes sharing easier. I often say that immutability is the default in the real world... I know things change in the world, but when we're dealing with information, the information is written on a piece of paper in the real world, and the paper doesn't change by itself. And if I need to share that information with someone, I make a photocopy of it and I give them a copy, right? So if I put a piece of paper in my pocket, I don't expect it to change, even if I shared it with someone else. And this is the kind of misconception that I think people have, because they're like "Oh, but my age changes" or "I can change my name. I can go and have my name changed at the courthouse." And all that is true, but the data about what your name was at that time shouldn't change, right? The record that we have of your name has not changed just because you went to the courthouse.
Eric Normand: They somehow want to mirror -- let me put it another way... All the information systems in the real world, like the pre-computer information systems that keep good records always focus on immutability. They go to great lengths to make things not change. So if you go to the doctor, and let's say you have the flu, they're gonna write that down, "Okay, we did a test. They have the flu. Prescribe some medicine, and they're gonna come back in two weeks." Okay, they come back in two weeks. Do they throw the record away though? "Oh, they've cured. Cross it out. Never had the flu." No, they write a new record that says "Okay, they've visited again. No flu, no symptoms. They're good." And then you put that in the file, too. You remember that they had the flu.
Eric Normand: So you are changing - you get the flu, and then you're healed. So you're changing, but the records shouldn't change, and they go to great lengths. There's all sorts of systems of files, and how do you store this, and how do you write it so that other people can read it? All this stuff... It's the same with accounting. If you spend ten dollars, they don't cross out your balance and write a new balance. Or worse, white it out, erase it, and write the new balance. No. They write a line. They deduct it, "They took out $10 from their account", so then they have to sum it up. They balance it at the end of the day.
Eric Normand: So all these information systems, which is what we're programming usually in software engineering - we're not (I guess) simulating the world. We're simulating the information system, the folders and the files and the papers. That's what we're trying to simulate. And all those - they go to great lengths to try to keep those records as long as possible, and they all have this append-only discipline. Not mutability. I think that's something that -- I mean, if I'm gonna be un-generous, object-oriented programming kind of damaged our thinking. I think OO is great for a lot of stuff, but the idea that you're gonna model the person and all their changing attributes as fields on an object - that's not what you're doing. You are modeling the medical record, and the doctor's visit, and the notes that the doctor writes during that visit - that's what you're modeling.
Eric Normand: The OO analysis and design has taught us to model the person, and that's not correct, so it's caused a lot of damage. And I think the FP people have been -- not immune, but just avoided that damage, that we're still programming information systems.
Lucas Dohmen: I find it interesting that you describe it that way, and that you also refer to OO there, because I think there's something similar in databases, right? If I look at a typical database, no matter if it's an SQL database or if it's MongoDB - if I change things, then it is overwritten. The old record is not there anymore. So maybe it's more than just object-oriented programming. Maybe it's going beyond that.
Eric Normand: So databases are really fascinating... All the databases that we use today were kind of seeded, the original ideas were like in the '70s, when disk space was really expensive. And so you couldn't afford to keep the record. You just couldn't afford it. I mean, this is the same problem in the year 2000, the Y2K problem. They were trying to save two characters by not having the 19... Because it was cheap. Those two characters with millions of records - that was expensive stuff. Now that we have really cheap hard drives, long-term storage, it doesn't make sense anymore... And the databases know this. They're mostly journaling now. They do add a record, "Oh, this row changed. This row changed. This row changed", and then those are later rolled up in a batch.
Eric Normand: So databases - the database implementers know that it's cheap enough to journal write-ahead log. They're appending all these changes to the database, instead of actually on disk, like, changing my name, they write a record somewhere that says "Okay, the name has changed to this now." And then later it kind of gets rolled up and eventually persisted to disk in the record itself... And they're only doing that to maintain backwards-compatibility, so that all the software that already assumes that the database is immutable can still work.
Eric Normand: The question is what would a database look like today, if it was designed today, where you have cheap hard drive... Basically, that's the definition of big data, right? It's cheaper to keep it around than to figure out what to delete... So you have cheap storage, and you know that it's actually pretty efficient to store this log, and you won't have to do the roll-up. What would it look like? And then that you wanna keep these records around forever. If you're writing an accounting system, you don't wanna delete the old stuff. You wanna keep it around, because you never know when the tax person is gonna come and say "What did the account look like on December 31st?" You wanna know that. So it'd be an interesting thing to do, to say "How would we design a database now?"
Lucas Dohmen: Yeah, I think that's also interesting. I asked you a few minutes ago about GDPR, and stuff... And I think most people don't think about it; their database keeps a write-ahead log, so even if you delete a person, it's still in the write-ahead log. It's just hidden from you, as a developer, but it's still there.
Eric Normand: And you could probably access it somehow; there's probably an API to get to it. But also, in the GDPR, if I'm not wrong, there are provisions for like -- you delete it in a reasonable amount of time... And the reasonable is up for interpretation, where you're like "Oh, we've made the delete, and then the write-ahead log will eventually..."
Lucas Dohmen: ...be compacted, right?
Eric Normand: Yeah, it'll compact it onto the disk. That's probably a reasonable amount of time, right?
Lucas Dohmen: Yeah, I agree. One question that a friend of mine asked when I said that I will talk to you was that -- because you have experience in both Clojure and Haskell, how does the type system influence your programming style? Do you write a different kind of functional programming in those two languages, or are they the same, but the type system is orthogonal to what you're doing?
Eric Normand: The styles of programming are different, for sure. I think the basic principles of functional programming are the same, and I kind of do think that the type system is orthogonal to FP. I think that a lot of people would disagree with me on that. They would say "No, you need a type system to do FP", but then that excludes so many people from FP that's it's not really fair. You're just arbitrarily excluding them because you don't like that style of programming.
Eric Normand: So I think the advantage of a Haskell type system - it's like having a logician on your shoulder. It's basically a system of logic that checks for consistency and says "Well, you said you would pass this only a string, and you're passing it a number here. Something's wrong." And often, it feels annoying, but then at some point you get to know this logician on your shoulder, and you can avoid the problems ahead of time, to be like "Okay, I know what he's gonna tell me here, so I'm gonna avoid it."
Eric Normand: That process for me took a long time. I think about six months of full-time work in Haskell before I felt confident that the code I would write was going to pass the compiler... And I often had these things like "Why not? Why don't you like this?" It just required a lot of experience, let's say, fighting the compiler... Until realizing "Okay, he's right. In this system, this is not correct." And I think that's very important, that I said, "In this system." It is only one possible system, and I don't think there is only one correct system. And that is one of the things that I think -- in an untyped language like Clojure, we recognize sometimes the system that is enforced by the language is not quite the system that you want. You want something equally rigorous, but different... And we have made the compromise that we would rather implement the system ourselves, in our heads... Be that logician. And have two logicians, like "For this part of the system we're gonna use this logician, and for this part of the system we're gonna use this logician." And that comes with trade-offs. Again, it's a discipline on you to do it correctly. A lot of people aren't gonna follow any particular discipline that's a sound, logical system... And so you see a lot of that. But from my personal experience - this is me - and this is a huge debate, and I don't feel like I’m gonna be able to solve it any time soon... But from my experience, it is easier to learn the Haskell type system, internalize it, and then when you move to Clojure, you have it in your head, and implement it as a discipline. And sometimes you implement it wrong, and you fix those bugs... It's not perfect. It's not like a compiler, and every time it gets the system right. I get a lot of the benefits from having that discipline in my Clojure code.
Eric Normand: So that's one system, or one process, where I learn the Haskell type system through trial and error, through trial by fire... And then when I'm in Clojure, because I've internalized it, I can do that. I can think that way and I can make my types line up. They're all in my head; it's not part of the language, but I'm doing it right. Versus - the other way is to learn all these cool tricks you do in Clojure; I guess you'd call them more dynamic. And then go to Haskell and try to implement them, and you're like "Oh, but the type system doesn't like it. There's no way. I can't figure out a good way to implement them so that the type system likes them." That's a lot less fun, and I don't even know if it's possible.
Eric Normand: That was one of the problems I had at first, like "I do functional programming, and I consider this a functional programming technique", doing this dispatch on type, and all this cool stuff... And I'd go to Haskell and it's like "Let me try this way." "No, it doesn't work." "Oh, I get why it doesn't work. Let me try it this way." "Oh, no, that doesn't work either." "Okay, let me try this one." "No, it doesn't work." "Well, I'm just not gonna do that." So I felt like I had to drop a lot of the Lispy, Clojury skills that I had, to program in Haskell. The other way, I feel like I've got the benefits of Haskell (some of them. 80%-90%, something like that) I've got them when I'm programming in Clojure. Plus, I've got all the other stuff.
Eric Normand: So that's my experience. It's why I prefer Clojure. But I do see the problem; when I look at other people's code, I'm like "Um, I don't like that you're returning sometimes numbers, sometimes strings, sometimes an array, or a vector, and sometimes null... Like, come on. Pick a type." This shouldn't have so many types.
Lucas Dohmen: Interesting. So you're basically saying that this logician on your shoulder - you make it a logician in your head that you can ignore if you want, and you can sometimes forget about it, but it doesn't need to be there and watch every step, right?
Eric Normand: Well, yes. I think that's a good way to put it. Most of the time I'm using it, because it makes a lot of sense not to return strings and numbers from a function... Like, what is a function supposed to do? If it's got all these weird -- this is, again, what I was talking about with the complexity... Does returning a number mean something different from returning a string? Maybe there is a better way to encode that meaning in some bigger construct than just the class, the type of thing that you're returning. Maybe you should have a record that has some name for the difference between the string and the number. Because what invariably happens is -- you know, like "Okay, I'm gonna return a string", and that means it's an error message. And a number means it's a success code. And that works fine. You can write the if statements and it works. But then later you're like "Oh, but now I need to write a success message. The code is not enough." So now I have a string for an error and a string for a -- like, how do I encode that? Because I just did it based on the string vs. number; now it's string vs. string, and there's no way to know, right? Or maybe you put “error;” and now you're parsing strings. That's terrible... Talk about if statements everywhere...
Eric Normand: And what if you return a string without an “error;” in it? You have this other problem. So you need some bigger type. Maybe it's a record that says "Status: success."
Eric Normand: Let's say you had to write the score of a sports game on a blackboard, and it's a sports game where you have a high score. Let's say you're writing 172. And I'm a reporter and I'm gonna read this thing and report to my newspaper or the radio this score. I'm reading it for the radio. I look over and you've already written 17, and I'm like "The score is 17!" And then you write the two, and I'm like "Oh no, I mean 172!" That's the kind of problem that we have in software... And it's invisible because you can't see that that happens, right? But you can imagine it happening, where someone is writing to this array while I'm reading from it, and I'm like "There's 14 elements!", but you're still adding as I'm saying that, right?
Eric Normand: So you avoid this huge class of problems, and this is one of those classes of problems that's multiplicative. The number of ways I can get that wrong increases factorially. As opposed to with types, and let's say getting the type wrong, and learning about that at runtime, it probably does increase more than linearly... But in my experience, those bugs would surface very quickly... As opposed to this one in a million chance that I won't see until production, and I can have no way to reproduce on my local machine; I am very likely to be able to reproduce someone returning an int instead of a string from a function. I can make that function return the int and see that.
Eric Normand: I think that that explains why I'm comfortable with dealing with the discipline of types in my head. It seems to me like something that you would catch all the time.
Eric Normand: Or if I do it, I catch it right away.
Lucas Dohmen: Yes.
Eric Normand: This is going deep into the debate - I do wanna bridge these two things. I think we don't talk enough; it really hurts me; it pains me that the two sides of the debate can't listen to each other and communicate... And I'm trying to listen to both sides, but it's painful, because anything I say gets interpreted. Like, "I didn't say that. I didn't mean that. You're assuming I'm on that side, and you're assuming this whole background. I'm not saying all that stuff."
Eric Normand: Anyway, I think that we need more love for each other... To see each other for who we are, and not -- okay, that's a separate podcast. But one thing that I think that is very useful to see is where does Clojure shine and where does Haskell shine? They're both really well-designed languages, and they have this major difference of static versus dynamic typing. Clojure shines really well when you’re dealing with unknown and changing data.
Eric Normand: There are some JSON APIs that I've used that if I was gonna type that JSON, meaning develop a type that fully represented all the possible responses I could get back, it would be a gigantic, un-understandable type. I would not be able to understand the type two weeks later if I got it right. What Clojure says is "Well, maybe you don't need to understand the type that well. There's parts that you can ignore. You can handle different parts in different branches..."
Eric Normand: I'm thinking for instance of the WordPress API - let's say it's returning a collection of something... A collection of blog posts because it's WordPress, right? Sometimes it will return an empty array, sometimes it will return null, and sometimes it will return false, or zero, or something like that. And you're like "Why don't you just return an empty array?" It's either got zero or more, you know? That seems to be the type, right? But it doesn't do that. It returns "False" sometimes. And sometimes the type of a field depends on the value of some other field. This is very hard to type, and you read the docs and that's not in there. You have to actually make the request and see what you get, and develop it kind of incrementally, like "Oh, okay, sometimes I'm gonna get a zero, so I've gotta check for that."
Eric Normand: So Clojure is better at that, like incremental -- sometimes "I'm gonna be strict on types, but only in certain branches." So you can be very flexible. Haskell is good for when you have figured out your domain and you want to tighten it out, and you know exactly how it's supposed to work, and you wanna avoid all the corner cases, and you wanna code that as a check very early in the compile stage; you can figure out all this logic about it and encode that as the type.
Eric Normand: I feel like what we need is the two extremes. What a lot of people try to do is come up with a compromise in the middle, where you have "You can do what you do in Clojure, but we're gonna kind of check it for big errors and big problems." That's not so useful. For instance, something might return a null and you didn't check that. Like, okay, that's useful, but is it worth the whole complexity of this type system to just know that?
Eric Normand: What you want is the two extremes. The "I'm gonna not check the types, and I'm gonna be able to very dynamic and go down different branches, and then at runtime make stuff make sense. Convert zeros to empty lists, and all that stuff - I'm gonna do that here." And then there's some things, even in Clojure, where I'm like "I know how this is supposed to work, and this needs to work right every time, and I want as much checks, and I don't want anybody to change it, I wanna lock it down with a type, I wanna never be able to write code that will break it... All these things. I want that, too." That's the other extreme. And I don't think you can -- you don't wanna compromise. You want the two extremes.
Eric Normand: I think in Haskell it's hard, but what it needs to do is let things be loose, like really loose, for certain classes of problems, like those kinds of loose JSON APIs. I've fielded a lot of complaints from Haskellers - arguments, let's call them - where they'll say "Well, how do you use an API if you don't know anything about it?" It's like "No, that's not the problem." You know a lot about it. It's just that is really hard to take all that you know about this JSON API and encode it as a type. It's really hard to do. It's got too many variables in it. And it's not about zero knowledge versus total knowledge. There is knowledge that doesn't fit the system. The system was not designed for this kind of loosey -- it's PHP, people. They're returning whatever. This is another dynamic language generating the API. It's an open source project, there's hundreds, thousands of contributors and changes to it, it's got backwards compatibility things, like they don't wanna change existing stuff; they made mistakes, they know it, but they have to keep it there, because there's clients... There's all these issues. It's evolving over time... So your type that you wrote today might not work tomorrow.
Lucas Dohmen: Yeah.
Eric Normand: And these are just the realities of working with these kinds of systems. I believe it's the kind of thing you would never be able to lock down with a good type. I've heard the same thing with people dealing with medical records. There are standards, but then the standards are evolving all the time, and even with the standards, they don't capture exactly what the doctor wants to say... And so the doctor will take a field that's supposed to be a number and they'll write N/A. Not Applicable. That wasn't captured in the standard that they should be able to do that, but they needed to do it for that particular patient. So now you get the CSV of all these medical record things, and it says it's supposed to be a number, we typed it this way, and now there's an N/A. What do you do? It depends on your application, but in Clojure we say "Well, let's not try to parse it yet. Maybe we don't even need that field for this particular task that we're doing. We don't even look at that field, so why even try to parse it as an integer? Just let it be. Just let it be." Or maybe at some point down the road something will know what to do with it, so we've gotta leave it, because they might convert the N/A to zero, or some other default that depends really on some other information that we'll have at a different stage.
Eric Normand: So we have a model that's loose for a reason, for dealing with these systems that aren't giving us perfect information, and the format is changing, and stuff like that. But there are a lot of systems where you know it, you know the model and you wanna bake it in. So that's my take. You want the extremes.
Lucas Dohmen: There's no right solution for everything, but I think that it's worth listening to what other people have learned in their field, in their language, in their framework, or whatever... And this is why I really enjoy conversations with people that are doing other programming languages, other fields, because I always learn something that I can apply in the languages I use, or the frameworks I use.
Eric Normand: Yeah, I think we do need -- we just need to be patient with each other, we need to listen to what the person is saying, and not assume... It's just like in politics; if a person makes an argument about some issue, listen to what they said. Don't assume "Oh, they are on this side of that issue, so I'm gonna put them into this camp, and they must also believe this, and that..." They didn't say those things. Just listen to what they say. And I know it's hard, because there's so much history of debate, and bad arguments, bad rhetoric about it, misunderstandings that have been voiced, and now you feel like you resent the other side for misunderstanding you, so now you're gonna not listen to them... We've gotta have some love, we've gotta come together.
Eric Normand: I actually should do a podcast about that, because I feel this very emotionally. I feel unlistened to, and I know I've made mistakes and I haven't listened to people, and I'm trying to listen... And it's not easy. Yes... And the idea of functional programming being something that you can take skills from, even if you're not in a functional language - I think that this is another thing that the functional programming community has been poor at communicating... That we often look at the most advanced stuff - you know, like Haskell, and some really far-out idea, that using an effect system, with applicative functors, and all this stuff... We see that as like THE answer, and anything less than that is just hacking. And it's saying that in an insulting way. I think that that undercuts all of the great skills that are fundamental to those ideas.
Eric Normand: Let me put it this way... I've been teaching Clojure for a while, and I would teach all these cool, far-out ideas thinking "Yeah, this is where it's at. Man, people are gonna love this." And then I'll get a question that's like "Oh, I'm having trouble with my code. It seems like I'm not really getting how to do this in Clojure", and I'm all ready to apply some new, cool thing... And I open up their code, and I realize "Oh, you are using global mutable variables, you're not using Map, Filter and Reduce... It's just really basic stuff that you need."
Eric Normand: I read a book that was addressed to the PHP community - it was basically how to use Map, Filter and Reduce in PHP. And it was a huge success, because no one had written that for them. No one had said "Hey, there's a lot of benefit just from this really basic idea." So that was an eye-opener for me for when I was writing Grokking Simplicity - no one has addressed these really basic things, like "How do you recognize if a function has side effects? And what do you do? What is a mitigation that you can do if you have these functional side effects? How do you start to make pure functions, functions without side effects from them? What parts are good to do that? Why would you do that?"
Eric Normand: I spent eight chapters on this, on pure functions, whereas most functional programming books will define it in a sentence or two at the beginning, and then go on to monads right away. It's like "Whoa, there's a lot there that --" No wonder it seems inaccessible to people. No one is writing the basics.
Lucas Dohmen: Yeah, true. But I also think that there is something that you need before you go into that. And I think that's that you don't order programming languages or paradigms in a hierarchy, like OO, and if you get better, then you go to FP, or... PHP is worse than other languages, right? I think there are definitely things that other programming language communities can learn from PHP, and a lot of people don't, because they think "Oh, PHP... What can I even learn from those people?" And I think that's a huge mistakes we're doing. That's why I think that things that we -- I think the things you wrote in the book, they are very basic for you, but they might not be basic for other people; but you are not writing it in a style that says "You should already know that. Why don't you know that?" I think that's valuable, and I think that's something that we all should do more - teach people things that we think are basic for us, without assuming that they are less smart because they don't know that, right?
Eric Normand: Right, right. I definitely believe all of that, and I tried to put that into Grokking Simplicity. I treat functional programming as a set of skills. So it’s a set of skills that might be foreign to you if you don't know functional programming... And you might know some of them, but it's a loose definition; it's skills that I see functional programmers do more than non-functional programmers. So it's a very loose definition, but these are all skills that -- I mean, every functional programmer I've talked to said "Oh yeah, these are important. I do these every day." So it's interesting to me that no one had defined functional programming in terms of these particular skills.
Eric Normand: I've had debates... I was having one yesterday about what is functional programming, and the stuff I'm saying about actions calculations and data - is that the definition of functional programming? They were saying no, and I'm saying "Well, maybe it's a good definition, maybe it's not. It's a working definition. It's the definition for the book." And regardless, if you ask any functional programmer "Is this important to know for functional programming?", they would all say yes. There's no doubt.
Eric Normand: Kind of inverting the question, it's like, let's not try to define it. Let's just say "What do all functional programmers know?" And let's start there. That seemed to really let the ideas bloom into the book.
Lucas Dohmen: Yeah. I also think that maybe it's also not very useful to define functional programming, right? Maybe it's more useful to just say "Here are a lot of ideas and tools that you can use", and you can use some of them. Maybe someone reads your book, and all they take away from it is structural sharing, for example. And they add it because it solves their problem. Or maybe defensive copying is solving the problem for them, because they have a legacy system which does weird stuff, and it helps them protect the application from that.
Lucas Dohmen: I think that's much more useful, to see the ideas and maybe a combination of those ideas is what helps you. Maybe one of those ideas is not helpful to you and you ignore it, right?
Eric Normand: That's right.
Lucas Dohmen: I think that's really nice.
Eric Normand: The all-or-nothing approach I think is too much to ask. It might be a useful ideal to move toward if you are dedicated to functional programming and you want your system to have all the benefits of it... Like, have this ideal in the future, like "We're gonna have no side effects etc" That's a useful ideal to move toward, but you've gotta start where people are. I think the movement toward the ideal is what I'm calling functional programming, not the ideal itself. You'll never get there. The “arrival is functional programming” that's not a good definition.
Lucas Dohmen: Yeah. Thank you so much for writing the book, I really enjoyed it. And thank you so much for this conversation.
Eric Normand: You're welcome.
Lucas Dohmen: We have a few eBook codes for people that want to read your book, and for the others, links to the book. So if you listened to the episode and you find one of those codes and they still work, then you get the book for free. Otherwise, you can check it out. So yeah, thank you so much for your time, and to everyone else, have a nice day.
Eric Normand: Thank you so much, it was a pleasure.