What does it mean for Actions to be first-class?

This is an episode of Thoughts on Functional Programming, a podcast by Eric Normand.

Subscribe: RSSApple PodcastsGoogle PlayOvercast

In Functional Programming, everything needs to be first class? But what does that mean? And why is it important? I discuss the idea of composing Actions and Calculations dynamically.

Why does everything in functional programming need to be first-class? What does it even mean to be first-class? That's what I'm going to be talking about today, my name is Eric Normand, and these are my thoughts on functional programming.

Before I talked about how there are the three domains of functional programming — there's data, calculations and actions. I mentioned that one of the minimum requirements to do functional programming, is that you can make those three things first-class.

I didn't go into very much about what that means. What does it mean to be first-class? Really it means that you should be able to have, in one sense a pointer or a reference to the thing.

https://twitter.com/ericnormand/status/1007276634192347136?ref_src=twsrc%5Etfw

Use it as a value that you can pass the calculations. That you can store a data. That you can return from calculations. This is the foundation of it. Just like you can pass an integer around, or you can pass a string around to functions in your language or the methods.

You need to be able to make an action and pass that to a function that is stored in a variable, something like that. Put it in a collection. You need to be able to have a handle on that. That is what allows for functional programming to be, to do composition.

Let me back up. In an imperative paradigm, when everything is a sequence of actions to take, a list of steps. The way you compose is by, in the code writing Do X. Then under that you write Do Y. Under that you write Do Z — those three things in order.

The main way of composing is through sequencing those actions. Even if you have multiple threads, you'll have a step that starts a thread. That's an action. Then things start happening in parallel but each thread is sequenced. The sequence is steps.

The thing is that's not first-class, because those steps are not something that you can have a pointer to. I'm not talking about a pointer to the code. I'm talking about making it an object in memory that you can use somewhere else.

https://twitter.com/ericnormand/status/1052635900432326658?ref_src=twsrc%5Etfw

A classic example is that in Java or JavaScript, most languages really, the IF statement is not first-class. It's just some syntax in your language that gets compiled into some machine code. That's the end of it. In a language like Smalltalk, the conditional was actually a method on an object.

An object was the thing you were testing. For instance, the TRUE, the object that represented the value TRUE would be called — If TRUE on it, it will call the first block that you've passed it. That's the THEN. If TRUE on FALSE, if it ignore the THEN and call the ELSE.

In that way, it was a method that you could then refer to somewhere else. You could say that IF, the conditional was first-class. There was nothing in Smalltalk besides message passing. In a functional language, you need to be able to make stuff first-class.

Usually, a language already has first-class constructs. As an example, in JavaScript, functions are the savior of the language in terms of doing functional programming, because you can always wrap non-first-class stuff up in a function. In JavaScript, the plus operator, addition is not first-class. Multiplication is not first-class. There's no way to further that plus symbol except in a mathematical expression.

https://twitter.com/ericnormand/status/1010900743006621697?ref_src=twsrc%5Etfw

You can't pass it to a function. You can't return plus. It's invalid syntax. You can take that plus and wrap it up in a function. You can do function P-L-U-S and then two arguments, A and B, and then return A+B. Now, you've recreated the plus as a function.

It does the same thing as the plus operator. It's identical like a function, but you can pass that around or return it, whatever. It's first-class. The same thing happens with actions. You have an action like print to the terminal, or send an HTTP GET request. Those actions could be wrapped in a function.

The function has zero arguments. When you invoke that function, it will send out the GET request. You don't have to invoke it. You can choose when to invoke it. It's just bundled up in this function ready to go.

We talked before about the problems in languages like JavaScript and Clojure where the language doesn't help you so much in determining what is an action, what is a calculation. You have functions representing both your actions and your calculations.

Those can get confusing. The same rules apply, the conceptual rules that two actions, when you compose them, make a new action. A calculation and a calculation make a calculation, but an action and a calculation make an action. We've talked about this before that actions infect the other stuff.

Calculations infect data as well. Why do we need to make stuff first-class? The real reason is you need to do real functional programming in the sense of getting past the trappings of immutable data and data transformation and stuff like that. That's very important. You don't get to the next level unless you have higher-order functions.

Higher-order functions simply means functions that are operating on other functions. Speaking loosely, I'll also include calculations that operate on calculations and actions. What does this mean? It means that instead of an imperative way, the way you compose actions is just through sequencing it in a code.

You can actually have two actions and pass them to a function that makes a new action that sequences them. Everything has to be first-class to do this. If you don't have a way of passing the actions to a function, then you don't have any way of returning a new action.

https://twitter.com/ericnormand/status/1049676397571166208?ref_src=twsrc%5Etfw

There's no way to do this. This lets you do interesting higher-order stuff. You don't have to write every function out. JavaScript is a bad example because you can always wrap an action up in a function.

In a language like C, or Basic is maybe even a better example, when you're creating these actions, there's no way to pass an action to a function and have it return a new thing that sequences the two actions. You have to write out every sequence that you want. Now you can do it hierarchically. You can have subroutines and stuff like that, but still you're writing it all out.

Whereas in a functional language where you have higher-order operations, those things can be done dynamically. You can have sequences of actions that were never written in code. You have code that generates those sequences. What's an example? You could have a program generate every possible action. I'll use my hands. Let's say this is a million different possible actions.

https://twitter.com/ericnormand/status/1005102188191014913?ref_src=twsrc%5Etfw

Filter them for the ones that are valid. OK, so now it's only 10. Then run those in sequence. How does it determine if it's valid? Well, it's got information about the environment that it's in now. You're dynamically generating this list of 10 actions that are valid. Then you can run them in sequence. You can't do that unless it's first-class.

Those are some of the things that we do in functional programming all the time. I should mention monads. A lot of languages use monads. Scala uses monads. They are a way of composing actions. That's not the only thing monads do. In the I/O type, which is the action type in Haskell, that is how things get sequenced. They are a special type that is recognized by the run time as these are actions.

You could think of it as there's one part of the language that it just sees this as another data type that happens to implement monad. When you use what's called the DO notation, which is a way of writing out monadic code, this is a shorthand like a syntactic sugar for writing out monad operations.

This is getting deep into Haskell. I don't know if I wanted to get this deep into monads right now. Monads are a way of composing values together. Haskell uses I/O, which has a monad instance. You can use it like a monad. You're actually composing actions together that way in Haskell.

In Clojure, in JavaScript, even if you're writing in a functional style inside of a Clojure function or a JavaScript function, you can always list steps. Very often we just have one expression in there, but you could list steps. They would happen in sequence. In Haskell there is no way to do that. There's no way to list steps. What you do is you use monadic composition to sequence steps.

That's all I wanted to say, that Haskell has this notion of first-class actions and a way to compose them up. Monads is one of the ways of composing them up. That's all I have to say about first-class values. It's very important to have first-class values. You want to be able to get to a higher-order. That's where a lot of the benefit of functional programming comes from.

https://twitter.com/ericnormand/status/1055111901162684416?ref_src=twsrc%5Etfw

You're writing code that composes sequences of actions. I didn't mention this, but higher-order functions can also take functions. You can have a calculation that takes calculations. A common example, map. Another one, filter. Another one, reduce. These are your three workhorses of functional sequence transformation. They all take functions as their arguments.

https://twitter.com/ericnormand/status/1012350035873419266?ref_src=twsrc%5Etfw

They let you reason at a higher level, write code that separates concerns out very well. You're separating out the iteration through a list and what you do to each element of the lists, how you build up your list. You're passing into map the function that says what to do to each item, or you're passing into filter the function that says which items to keep.

The filter logic can be done once, written one time, and used in many, many cases. The same for map, this idea of building a new list based on an old list with all the elements transformed. That can be written once and reused many times.

This allows us to have code that is reusing stuff that, in a language without that first-class, you would be writing inline every single time, like a four-loop or something.

Oh, how many times have you written a four-loop that takes one list and changes the elements of the list in the same way for each element in the list? It's such common code. There's almost no way to get rid of that unless you bring it up to a higher level where you can pass in that body of the loop as a function, as a calculation.

All right, so that's the other part of being first-class, is getting to higher-order functions. Not just ability to dynamically sequence actions but also to have higher-order functions. There's plenty to talk about about higher-order functions, which I'll get to in another episode.

https://twitter.com/ericnormand/status/1078743024862195712

If you want to hear about that, please do subscribe. Like it. Star it. Heart it. Thumbs-up it. Plus-one it. Do what it says.

Favor it. What else is there? I don't know. High five? Does anyone use a high five? Do that one, too. Anyway, this has been a pleasure. My name is Eric Normand. These have been my thoughts on functional programming. See you later.