I mentioned in a previous post that I am developing a new course on functional programming for first-year students. The immediate question, which none of you asked, is “what do you mean by functional programming?”. Good question! Now, if only I had a crisp answer, I’d be in good shape.
Socially speaking, the first problem I run into is a fundamental misunderstanding about the term “functional programming”. Most people think that it stands for an ideological position that is defined in opposition to what everyone else thinks and does. So in the mind of a typical colleague, when I speak of functional programming, she hears “deviant religious doctrine” and “knows” instinctively to disregard it. Yet what I mean by functional programming subsumes what everyone else is doing as a narrow special case. For as readers of this blog surely know, there is no better imperative language than a functional language! In fact I often say that Haskell is the world’s best imperative programming language; I’m only half (if that much) joking.
So if functional programming subsumes imperative programming, then what the hell am I talking about when I advocate for functional programming at the introductory level? And why, then, do we also have an imperative programming course? Very good questions. I’m not sure I have a definitive answer, but this being a blog, I can air my opinions.
Were it up to me, we would have one introductory course, probably two semesters long, on programming, which would, of course, emphasize functional as well as imperative techniques, all in one language. Personally, I think this would be the right way to go, and would prepare the students for all future courses in our curriculum, and for work out there in the “real world.” Not everyone believes me (and sometimes I am not sure I believe myself), so we must compromise. While it surely sounds all nice and ecumenical to teach both imperative and functional programming, the reality is that we’re teaching old and new programming methods. It’s not so much the imperative part that counts, but their obsolescence. It is deemed important (and, to be honest, I can see the merit in the argument) that students learn the “old ways” because that’s how most of the world works. So we have to use a language that is ill-defined (admits programs that cannot be given any meaning in terms of the language itself), that forces sequential, rather than parallel thinking, that demands manual allocation of memory, and that introduces all sorts of complications, such as null pointers, that need not exist at all. And then we have to teach about things called “tools” that help manage the mess we’ve created—tools that do things like recover from Boolean blindness or check for null pointer errors. To be sure, these tools are totally amazing, but then so are hot metal typesetting machines and steam locomotives.
So what, then, is a functional language? I can do no better than list some criteria that are important for what I do; the languages that satisfy these criteria are commonly called functional languages, so that’s what I’ll call them too.
- It should have a well-defined semantics expressed in terms of the programs you write, not in terms of how it is “compiled” onto some hypothetical hardware and software platform. I want to be able to reason about the code I’m writing, not about the code the compiler writes. And I want to define the meaning of my program in terms of the constructs I’m using, not in terms of some supposed underlying mechanism that implements it. This is the essence of abstraction.
- It should support both computation by evaluation and computation by execution. Evaluation is a smooth generalization of high school- and university-level mathematics, and is defined in terms of standard mathematical concepts such as tuples, functions, numbers, and so forth. Variables are given meaning by substitution, and evaluation consists of simplifying expressions. Execution is the action of a program on another agent or data structure conceived of as existing independently of the program itself. The data lives “over there” and the program “over here” diddles that data. Assignables (my word for what in imperative languges are wrongly called variables) are given meaning by get and put operations (fetch and store), not by substitution. Execution consists of performing these operations.
- It should be natural to consider both persistent and ephemeral data structures. This is essentially a restatement of (2). Persistent data structures are values that can be manipulated like any other value, regardless of their apparent “size” or “complexity”. We can as easily pass functions as arguments and return them as results as we can do the same with integers. We can think of trees or streams as values in the same manner. Ephemeral data structures are what you learn about in textbooks. They are external to the program, and manipulated destructively by execution. These are useful, but not nearly as central as the current texts would have you believe (for lack of consideration of any other kind).
- It should have a natural parallel cost model based on the dependencies among computations so that one may talk about parallel algorithms and their complexity as naturally as one currently discusses sequential algorithms. Imperative-only programming languages fail this criterion miserably, because there are too many implicit dependencies among the components of a computation, with the result that sequentiality is forced on you by the artificial constraints of imperative thinking.
- It should have a rich type structure that permits introduction of new abstract types and which supports modular program development. By giving short shrift to expressions and evaluation, imperative language have an impoverished notion of type—and not support for modularity. Worse, object-oriented programming, a species of imperative programming, is fundamentally antimodular because of the absurd emphasis on inheritance and the reliance on class-based organizations in which functionality metastasizes throughout a program.
What languages satisfy these criteria best? Principally ML (both Standard ML and Objective Caml, the “objective” part notwithstanding), and Haskell. (Unfortunately, Haskell loses on the cost model, about which more in another post.)
This is why we teach ML.