Emacs fulfills the UNIX Philosophy
Part 4: Lisp does FP better than UNIX shell programming
This is part 4 of a 6-part series of articles defining what the UNIX philosophy is, what Emacs is, and discussing whether Emacs fulfills the UNIX philosophy.
0. Introduction 1. Emacs is an app platform 2. What is the UNIX philosophy 3. Seems like Functional Programming 4. Lisp does FP better than UNIX shell programming 5. The parallel histories of UNIX and Lisp 6. Response to common criticisms
In the part 3 of
this series I talked about how the UNIX Philosophy, the idea
that every program does one thing, and does it well
have
several defining principles in common with functional programming
(FP). In particular, functions in FP should also do one thing and
do it well
, so programs
and functions
are
conceptually the same. But to do FP, you must be able to compose
functions together using higher-order functions.
I will take for granted the fact that Lisp is commonly
acknowledge to be a FP language, so I will not explain why Lisp is a
FP language. This article is more about how UNIX shell programming,
the Bourne family of programming languages, are not FP
languages, and there are only a few built-in language constructs for
composing programs, like the pipe operator
(
). This proves to be quite the limitation when
it comes to composing programs. When composing programs or functions
together to solve larger problems, a Lisp programming language has
an advantage over a Bourne-style programming language. Refer to
the Eshell
programming language, which combines a Bourne shell-like language
with Emacs Lisp, as just on example of how Lisp can improve on the
UNIX Programming Environment.|
My goal here is not to evangelize Emacs. I readily admit Emacs is
far from perfect, and that there are certainly better
implementations of FP (Common Lisp, Scheme, and I especially love
Haskell). Rather my arguments is that there are better ways of
designing programs/functions in a way that they do on thing and
do it well,
without designing your programs/functions for use in
a UNIX shell. It just so happens that Emacs is one of the oldest
Lisp implementations still in wide-spread use that was originally
implemented for UNIX OS and it's clones. Decades of evolution
have made Emacs one of the most practical means of integrating
Lisp-style FP within
the UNIX
Programming Environment.
I also think I should mention that when Emacs is used as a shell in place of Bash, you have access to a wider range of FP tools. You still have access to all of the shell programs on your computer, but you also have access to Lisp functions that compose in other ways. You can, for example, capture the output of a command in a text buffer, then start applying both Lisp functions and UNIX shell programs as filters to the text buffer, seeing the result of each transformation as it happens. This allows you to experiment with function composition interactively, but without necessarily being restricted to the use of the REPL.
Emacs is less than perfect, Bash even less so
In a UNIX Programming Environment, the Bourne shell family of languages (like Bash) is the language in which you compose different programs into a solution to a problem. It doesn't have to be Bash, any language using the POSIX API can compose shell pipelines. But in practical, day-to-day UNIX or Linux system usage, it is usually Bash or some Bourne-family shell language.
Unfortunately, the Bourne shell languages are simply not very good programming languages. This is not even a very controversial opinion in my experience, so I won't spend too much time explaining why. But I can mention a few of the problems:
String is the only data type. Structured data types must be implemented by each respective program, and programs do not always implement data structures consistently (e.g. CSV, JSON, or XML), or in ways that can share code.
Process forking is CPU and memory intensive. This makes the shell interpreter terminally slow.
No enforcement of calling conventions. Each program defines it's own argument parsing convention, which is why most programs take double-hyphen arguments, while some programs like
find
take single-hyphen, and some likedd
take no hyphens.ls --inode --size --quote-name --color=always; find . -type d -name .git -prune -false -o -type f -name '*.lisp'; dd if=/dev/urandom of=./white-noise.raw bs=44100 count=5;
No built-in support for atomic operations. Asynchronous coroutines are error-prone, race conditions on the standard output channel can occur as data from mutliple asynchronous programs write to it. Line buffering solves this problem most of the time.
The syntax is crufty and inelegant. This is an opinion of aesthetics, but in my experience most people agree with this statement. With practice, you can remember all the various curly-brace expansions, and understand why the following two lines of code do almost the exact same thing:
if [[ "${#LIST[@]}" > 10 ]]; then ... ; fi if [ "${#LIST[@]}" -gt 10 ]; then ... ; fi
Still, these syntactic idiosyncrasies really bother some people.
Personally, I do enjoy Bash programming, but I always conscious of it's shortcomings. I would only ever use it to perform transformations on a small filesystem, or as a lightweight wrapper around an application written in a better programming language. Modern Lisp languages have solved all of these problems, and scale well to fit applications of any complexity.
Conclusions
In the part
3 of this series I talked about how the UNIX Philosophy is
really a particular description of functional programming. In this
article (part 4) I talk about how UNIX shell programming is not the
best way to do functional programming, and suggest that using a Lisp
programming language such as Emacs is a better way to devise
software solutions to the various problems you may encounter as you
use a computer. If you are willing to accept each of those claims as
truth, then you might also be willing to accept that Emacs also, in
fact, does fulfill the UNIX Philosophy even better than the UNIX
shell programming environment. It is just a matter of understanding
that every program does one thing and does it well
not only
applies to UNIX shell programs, but to Emacs Lisp commands as
well. And hopefully you can also see that using Emacs gives you
access to both sets of tools (all of which do one thing well): the
UNIX CLI tools, and all of the Emacs Lisp tools.
It seems like perhaps UNIX may have been inspired by Lisp, since both UNIX and Lisp have roots in the MIT CSAIL laboratory, both were first developed during roughly the same time period (Lisp in the late 1950s, UNIX in the late 1960s). In the next section I go into the history of both Lisp and UNIX, and I conclude that, no, UNIX took no inspiration from Lisp. However innovations in computer technology at the time did influence the design of both UNIX and Lisp in similar ways, especially with regard to what we now call the Read-Eval-Print Loop (REPL).