What is point-free style?
This is an episode of The Eric Normand Podcast.
Subscribe: RSSApple PodcastsGoogle PlayOvercast
Point-free style is a way of defining functions with a very simple constraint: you cannot name arguments or intermediate values. How can you possibly do that? Well, with higher-order functions, of course. For instance, with function composition, you can define a new function without naming the arguments. Some languages, like the APL family, or Haskell, let you do this very easily.
Eric Normand: What is point-free style, and does it have a point? By the end of this episode, you'll know what point-free style is. How you might already be using it and why it makes things hard to read sometimes.
Hi, my name is Eric Normand and I help people thrive with functional programming. Point-free style, it's an important term because you might come across it when you're reading about functional programming, or in a discussion with someone who is into functional programming.
It's good to be familiar with it. It's also an important style for programming, because it helps you think at the right level of abstraction. These things are good to name. This is where my head is.
That said, it's not like once you learn this, you should be doing point-free style everywhere. Except maybe as an exercise. It is not "The last style" that we should be learning, it's nothing like that.
What is point-free style? It means you're programming in a way that you don't need to name your arguments or name any of your intermediate values. When you define a function, normally, you have to name the arguments. Most languages require it, or they make that the really easy way to define a function.
Likewise, when you're in the body of the function, you're writing what this function does. Often the easiest way to use the result of calling a function is to save it to a variable, and then pass that variable to the next function. Use it later in the expression.
Point-free style says, "Can we do without that? Naming is hard, it takes a lot of room. It's a line per variable. Maybe we don't want to do that?"
How do you do this? The easiest example to really hit home is, let's say you're calling map, and you're calling map with something like toUpperCase, the function toUpperCase. On a list of strings, you're mapping toUpperCase over those strings.
One thing you could do is you could define...you're calling map, you're passing in a function, so you're going to define a new function, give it an argument, maybe call it S because it's a string. Then you call toUpperCase on that string and return it. Why did you wrap toUpperCase in a new function? It already is a function.
I know in some cases you need it because the map passes both the value and the index, and you'd want to ignore the index. There's reasons for some of it but a lot of times it's unnecessary.
Removing that extra layer where you have to name the argument and just pass in the function itself, it's a way to achieve point-free style. Point-free style has a bunch of little techniques that remove the need for arguments, one little step at a time until you got no arguments. That's the point-free.
You could have point-less, point-minimal style where you have one or two arguments left maybe. It seems like a spectrum, that's what I'm trying to say. It seems like a spectrum, you can get down to point-free style, but you could move along in that direction.
Another one that we've already talked about in a previous episode is function composition. Function composition in the general case is, you have two functions, you want to take the return value of one and pass it to the other.
So you make a new function, it takes the arguments, passes them to the first function, saves the return value then calls the second function with that return value, then calls the second function with that return value, and then returns it.
Well, there's a special case where the second function only takes one argument. The return value can go right in there. You don't even need to wrap it in a new function that you write explicitly. You can put all that boilerplate into another function, a higher-order function that takes two functions and returns a new function.
Notice when you do that, you've totally eliminated naming the arguments or the intermediate values. You just say, "Compose f g or f.g," depending on your language and how you do it.
Instead of writing new function that takes x, calls g of x, and then passes that to f and returns it. You just write, Compose f g. You've eliminated the argument. Function composition is used a lot in point-free style. Another way that's very related to function composition is pipelining. In Clojure, you have a thing called the threading macros, it's a bunch of things.
It's a bunch of different macros that do threading. What it lets you do is write pretty long function composition expressions, but in a top-down, like one line at a time way, where the return value of one thing is passed to the next thing, is passed to the next thing, is passed to the next thing. You never have to name those things. This is a way of achieving point-free style.
I believe Elixir has something very similar. I don't know if they call it threading or pipeline, something like that, but it's the pipe...Oh man, am I going to get this wrong? I think it's pipe greater than sign. Elm has something similar as well, it just lets you flow values through these functions. You don't have to name any of those intermediate values. Why would you want to do this? Why?
Why is this a thing worth naming? Two reasons, really. Naming is hard, so having to come up with all these variable names is a thing that if you can avoid it, and without much cost, it might be worth avoiding naming. The names take up more room, so you have longer code.
There're things that could get out of sync more easily, meaning, I've had a thing where I've called the variable, five days, because the first time I wrote it, it was five days, like a unit of time.
Then I changed it. Now, the function that I'm assigning to that variable is returning seven days. I need to change the variable name. I need to change the variable name and then go and find everywhere I use it and change it. I named it wrong. Naming is hard, I named it wrong. I could have avoided that maybe, and not have to deal with it, at all.
Then there's this other more important reason besides not having to do this hard thing called naming. The other thing is that when you're doing point-free style, you're often working at a higher level of abstraction. Let's call it the right level of abstraction. You're like really in the zone, you've got the program in your head. You're thinking, "Oh, hey, I need to compose these two functions."
You don't want to be thinking, "OK, now I have to define a new function give it an argument name and thread this argument in here." You don't want to do all that. You want to think, "Oh, I'm just composing these two. Done." In Haskell, it's one character, .f.g, boom. A period. The full stop.
It lets you express yourself at that higher level just like higher-order functions too. It's done with higher-order functions. Point-free style is done with higher-order functions. This is taking it to the extreme where you'd never name arguments. That's the style.
Just like you can get into this flow where you've got really good concentration that day, you got the whole thing in your head and you're working really efficiently. It's becoming almost a very clever code.
That doesn't mean that tomorrow, you're going to be in that same headspace, a week from now, or certainly, six months from now. This style can be very terse. It relies often on operator precedence rules and other knowledge of how things work, like how currying will work, things like that. What happens is you come back to it and you don't understand it anymore.
Looking at some Haskell point-free code for examples, I used to work in Haskell. I would write stuff like this sometimes. There's a lot of parentheses around things. There'll be a parentheses around the compose operator because you want to force the precedence somehow.
You're passing the compose and you don't want the compose to run. You want to pass compose. You put parentheses around it. You get some really, what I would consider, gnarly, gnarly code. There's something about the redundancy of names that makes it easier to read sometimes. I think it's like a noble pursuit or it's a good exercise to try to push the style to its extreme. Just like an architecture.
You might say, "Let's take some architectural principle and push it to the extreme, see what we get, what building we will design if we just say," I'm just going to make something up, "All the vertical structure is going to be made of steel and all the horizontal structure is going to be made of wood."
Just very, very basic rules and constraints like that. You see what buildings you make out of that. It's a great exercise because it's forcing you to explore in this new way. That doesn't mean it will make a good building. It doesn't mean people will want to live in the building or work in that building. But it's a good exercise.
I think that's true of point-free style. It's a good exercise. It causes you to think of this higher-order function level. It doesn't mean the code you write is the code you want to live with in your system. That's my opinion there.
The guy who wrote the Dart programming language, he has said that he thinks you really can't do point-free style and currying, which is used in point-free style a lot.
You can't really do them without a good type system because you lose so much information when you eliminate the arguments and the names of the intermediate values, that you can't tell yourself when you're looking at the function whether it's correct or not, whether you've made a mistake.
Just as an example, if you wanted to write a sum function in point-free style in Haskell because Haskell lets you do that, you say, sum = foldr, if you're doing a foldr, + 0. OK, that's point-free style. I had not named any arguments. Typically, if you're going to do a foldr, you can say sum of the list is equal to foldr + 0, list. You're naming the list argument. You don't have to because of currying.
Now, when you read that, you have to know how many arguments foldr takes. You might know it. I'm not saying you don't know that, but what if it was a different function? What if it wasn't foldr? What if it's some other function that you don't know? You don't know if there's some currying going on because you don't know how many arguments that function has.
You can look at the type, but that's it. That's what it's saying that it relies on good type information that your compiler can check.
I tend to agree with that. If you go that far, you lose so much information for your brain to pick up on that you really need the help of the type system. Just look at some of the point-free style stuff that people had done. You have all these operators, dots and greater than, less than operators.
You're like, "What does this do? How many arguments does it take? What's the precedence rules? I can't figure out why you put those parentheses there." It's very terse.
That said, I think it's a spectrum. You can go to this extreme, but also there might be somewhere along that spectrum that is perfect, has very clear code and does not have unnecessary names, but has some names to give you clues what the code is supposed to do.
Another thing about it is because you're working at this high-level, this is a good thing. You're working at this high-level, you or the compiler can begin to do algebraic manipulations on the functions. You're at a higher order. You're dealing with functions of functions. These functions can have algebraic identities, algebraic properties.
You could say, just a simple example, that map of F over a list is equivalent to the list of f of each element of the list. That's like an identity. Your compiler might say, "Oh, because I'm working on a higher-order function, I understand how map works."
I can replace this map with something else, with some other code that is equivalent to it. I don't have to actually call the function "map" myself.
There's a bunch of these identities. In fact, that is where the programming language J was going. The idea was that you couldn't define functions with defn or def function.
There was no way to just make a function. You had to do it with these higher-order combinators. You would be able to have all these algebraic identities that would let the compiler optimize and do all sorts of cool stuff.
Imagine you could only do map, filter and reduce, and not define new functions. You're just mapping and filtering with existing functions, and compose. Obviously, you can do stuff like that, compose.
The languages that use this the most...this is my experience. My little bit of research has turned these things up. This is where I have encountered it the most.
There's the APL family of languages. APL, famous for being very terse and not having to name arguments. J is a member of the APL family. Look at some APL code and you'll see how terse it can get.
Haskell lets you work on the whole spectrum. You can define all your arguments with names and intermediate values with names, or you can move more toward the point-free style if you want.
I already mentioned in Clojure and Elixir, there's the thread or pipeline stuff. I just want to mention names can be bad, but they can be really good for readability. They can be a pain to write, but really helpful when you're reading your code.
Just to recap...I'm going long, so I'm just going to recap real quick. Point-free style means programming where you don't have to name arguments or intermediate values. The main reason we do it is to be able to code at the right level of abstraction.
You don't want to be thinking about, "How do I name this function?" or "How do I name this argument?" Stuff like that. You want to be thinking higher order like, "OK. This is the composition of this. This is mapping that over this." That's what you're thinking.
It creates some really terse code that can be obtuse, hard to understand. APL family, Haskell, Clojure, and Elixir, those all use it.
I want to ask you, are you using point-free style anywhere in your code? Have you managed to eliminate an argument here or there that you didn't need to name?
I'm also curious because there's some really [laughs] opaque code out there that's in point-free style. I'm wondering what the worst you've seen is.
Send that to me. You can email me at firstname.lastname@example.org or you can tweet me @ericnormand. Find me on LinkedIn. Please subscribe if you liked this because there's more coming down the pipe.
I'm Eric Normand. I'll see you later.