Power Isn't Everything

It is a logical impossibility to make a language more powerful by omitting features, no matter how bad they may be. – John Hughes

I recently read Why Functional Programming Matters by John Hughes. When I first read it, I don't think I had the maturity to really grasp the message. This reading felt like it made a lot more sense to me. The paper argues that modularity is important to building programs and that functional programming offers advantages in making modular code. The functional programming described in the paper uses Miranda and is roughly Haskell: statically typed, lazily evaluated and pure.

The paper is built around the premise that functional programming lacks side-effects and despite removing power from the language, it is made up for in the new glue. This new glue makes modular programming easier but depends on the lack of side-effects. Structured programming is given as an analogy, which was defined roughly by the lack of GOTO, which removed power, however it helps modularity so it's generally believed to have been a price worth paying. Regardless of if the parts about functional programming are true, this bit about the relationship between features and power is important, I think. It effects everything, from software being correct, to being understandable, and to being secure.

The functional programmer sounds rather like a mediƦval monk, denying himself the pleasures of life in the hope that it will make him virtuous. – John Hughes

Why Functional Programming Matters was published in 1990 and since then software has gone the path of more power is better. Most software, including programming languages, I come in contact with have a clear trend of consuming more features. Take, for example, C++11, which many people were very happy about. C++11 got a lot of praise for the things it added but it did not remove anything, making it a more complicated language. Java 8 is in a similar situation, it added streams and lambdas but didn't remove a thing. Maybe there should be a law that for every three features a language adds it has to remove one old feature.

Tools that are meant to help me do things correctly can be a special source of disappointment. Chef is "just" Ruby scripts with access to a particular set of libraries. Since they are Ruby scripts, Chef modules can do anything, even things that they definitely shouldn't do. Chef is so expressive that variables have 15 layers of precedence. There are no constraints in Chef, so any module can effect, and be affected by any other module. A configuration management tool is meant to help me setup machines correctly, yet in choosing power it has made it difficult to know if I am using it correctly.

Even tools I generally enjoy, like Emacs and Ocaml, continue to get new features and are reluctant to remove old ones. The object system of Ocaml may be elegant, however it is so infrequently used that the cost of keeping it around is probably not worth it.

The structured programming analogy given in the paper is an example of the cost of power when it comes to understandability. Structured programming has not prevailed over GOTO because it's a four letter word but because structure helps people understand their programs better. It's easier to determine if a program is correct when it is structured rather than jumping around unfettered.

In my opinion, another place this is true is static typing. The benefit of static typing is being able to understand if a program is correct in certain ways. Dynamic typing is more powerful than static typing: a developer can express a lot more things but at the cost that they can even express ones that make no sense.

There are many cases where reducing power creates a less vulnerable product. Maybe all? The ImageTragick CVE is a recent example of this. And this is nothing new. Think of all the web browser security issues related to JavaScript, a huge increase in power on the web. Security APIs like pledge are designed around letting a program voluntarily reduce its power.

My approach to handling power is to put the most power at the leaves of a program, where it has the least knowledge. The driver is often a framework with limited capabilities and clear semantics, maybe even a DSL, which calls out to the leaves to perform the heavy lifting. This makes it straight forward to understand how the program can flow. The leaves are limited in their scope, so even though they are the most powerful they are meant to do very little. It's not always possible but segregating a program in this way has been a helpful way to keep it simple but still solve complicated problems. This matches how almost all programming languages provide a restricted representation of the underlying machine but if you follow any code path far enough, it eventually is being converted to the all-powerful machine code.

Author: orbitz
Updated: 2016-06-25 Sat 16:58