Every text editor has a certain amount of essential complexity.
At minimum, it has to maintain an internal buffer copy of the file or
files the user is editing. Functions to import and export file data
are a minimum requirement (usually from and to disk, though the stream
editor
sed(1)
is an interesting exception). Some way to modify the buffer must be
supported, though we cannot specify what way without describing
specific features that are optional. Our four examples show widely
varying levels of optional and accidental complexity beyond
this.
Of all of these,
ed(1)
has the least complexity. Almost the only non-orthogonal feature in
its command set is the fact that many of its commands can take a
‘p’ or ‘l’ suffix to print or list command
results. Even after three decades of feature additions there are
fewer than thirty editing commands, and the normal working set for
most users will be less than a dozen. There is not much in the way of
optional complexity that could be removed here, and it's hard to
identify any accidental complexity at all. The user interface of
ed is strictly compact.
On the flip side, the ed interface is
not really suitable for editing tasks even as basic as rapidly
flipping through a text file. One has to limit one's objectives
pretty sharply for ed to become an
acceptable solution for interactive editing.
Suppose, then, that we add “support visual browsing and
editing of multiple files” as an objective? Then
Sam seems not very far from being the
minimal ed extension that could achieve
this. The fact that the designers did not change the semantics of the
inherited ed commands is notable; they kept
an existing, orthogonal set and added a relatively small set of
capabilities that are themselves orthogonal.
One large increase in optional (implementation) complexity is
Sam's infinite-undo capability. Another
significant one is the new regular-expression-based loop and iteration
facility in the command language. These, and the fact that the mouse
can be used as a selection device, are about all that distinguish
Sam from a hypothetical
ed with a mouse-and-windows
interface.
Without a thorough code audit it's difficult to be sure, but at
the design level it's hard to identify any accidental complexity in
Sam. The interface is at least semi-compact and arguably strictly compact.
This editor lives up to the very highest standards of Unix design
— unsurprisingly, given its provenance.
By contrast, vi looks rather bloated
and flabby. There are hundreds of commands, many of them
duplicative. These are at best optional complexity, and perhaps
accidental. At a guess, most users don't know more than 5% of the
command set. With the example of Sam
before us, it's fair to wonder why the interface complexity of
vi is so high.
In Chapter 11 we
described the effect of the absence of standard arrow keys on early
roguelike programs; vi was one of these. When vi was built, its
author knew that many of his users would need to be able to use the
cursor motion keys traditional on Unix glass teletypes. This made a
modal interface inevitable. Once the hjkl keys had mode-dependent
meanings in an edit buffer, it was all too easy to fall into the habit
of adding new commands in an ad-hoc way.
Sam, designed as it is to depend on a
bitmapped display with both arrow keys and a mouse, can be much
cleaner. And it is.
But the clutter of vi commands is a relatively superficial
problem. It's interface complexity, yes, but of a kind most users can
and do ignore (the interface is semi-compact in the sense we developed
in Chapter 4). The deeper problem is an
adhocity trap. Over the years, vi has had progressively more and more
special-purpose C code bolted onto it to perform tasks that Sam
refuses to do and that Emacs would attack with Lisp code modules and
subprocess control. The extensions are not, as in Emacs, libraries
loaded as needed; users pay the overhead for the resulting code bloat
all the time. As a result, the size difference between a modern vi
and a modern Emacs is not nearly as great as one might expect; in
mid-2003 on an Intel-architecture machine, it's 1500KB for GNU Emacs
versus 900KB for vim. There is a whole lot of both optional and
accidental complexity in that 900KB.
For vi partisans, not having an embedded scripting language
— not being Emacs — has become an identity issue, a central
part of the shared myth that vi is a lightweight editor. While vi
fans like to talk about filtering buffers with external programs and
scripts to do what Emacs's embedded scripting does, the reality is
that vi's “!” command cannot filter regions of an edit
buffer selected at finer granularity than a range of lines (Sam and
Wily, though they have no more subprocess management than vi does, can
at least filter arbitrary text ranges, not just line ranges). All
knowledge of file formats and syntaxes that vary at a finer
granularity (and most do) has to be built in to C code if vi is going
to have it available at all. There is thus little prospect that the
codebase-size ratio between Emacs and vi will improve in favor of vi;
indeed, it seems likely to get worse.
Emacs is sufficiently large, and has
a sufficiently tangled history, to make separating its optional from
its accidental complexity quite a challenge. We can at least begin by
trying to separate the dispensable accidents of the
Emacs design from its indispensable
essentials.
Perhaps the most conspicuously dispensable part of the
Emacs design is Emacs
Lisp. It
is essential to what Emacs does that it
features what we nowadays call an embedded scripting language, but
Emacs would be little different in
capability if that language had been Python or Java or Perl. At the
time Emacs was designed in the 1970s,
however, Lisp was about the only language that had the characteristics
(including unlimited-extent types and garbage collection) to fit it to
the job.
Much in the particulars of the way
emacs handles event processing and drives a
bitmapped display (including the support for internationalization) is
accidental as well. The one great schism in its history (the GNU
Emacs/XEmacs fork) was over these issues, and demonstrates that
nothing in the rest of the design prefers or requires any one event
model.
On the other hand, the ability to bind arbitrary event
sequences to arbitrary built-in or user-defined functions is
indispensable. The scripting language could change and the event model
could change, but without the anything-goes polymorphism in the way
they are connected, the Emacs design would
be both unrecognizable and crippled. Extension modes would have
to fight each other for ownership of a limited event set, and
activating multiple cooperating modes on the same buffer would
be difficult or impossible.
The huge library of extension modes shipped with
Emacs is accidental as well. The
ability to construct such extensions may be
essential, but the particular set we have is a product of history and
chance. They could all be different or replaced; the result would
still, recognizably, be Emacs.
But subprocess interaction is indispensable. Without it,
Emacs modes could not perform the expected
IDE-like integration and front-ending of many different tools.
Experience with small editors that clone the default keybindings
and appearance of Emacs without emulating
its extensibility is instructive. There have been several such
clones, of which the best known are probably
MicroEmacs and
pico, but none have ever acquired
significant mindshare.
Having identified accident and essence in the
Emacs design helps us get a handle on which
of its complexity is optional and which accidental. But, more
importantly, they help us see past the superficial differences between
Emacs and the previous three editors we
have considered, to the really critical difference: the fact that the
objectives of the Emacs design are far more
broad. Emacs wants to be a unified
interface to all tools that operate on text.
Wily makes an interesting contrast with Emacs. As with Sam, the
amount of optional complexity is low; the Wily user interface can be
succinctly but effectively described in a single page.
But this elegance comes with a price; it is not possible to bind
functions to any keystrokes or input gestures other than a restricted
set of mouse chords. Instead, every editor function other than very
basic text insertion and deletion has to be implemented with a program
outboard of the editor, either a standalone script or a specialized
symbiont process listening to Wily input events. (The former
technique relies on outboard program startups being fast enough not to
produce noticeable interface lag, something which was emphatically not
the case in either Emacs's natal environment or under the Unixes it
was first ported to.)
Optional complexity which Emacs would
implement in Lisp extension modes is instead distributed through
specialized symbionts; each has to know the special
Wily messaging interface. An advantage of
this approach is that such symbionts can be written in any language
the user chooses. In addition, the symbionts (because they run
outboard) cannot adversely affect each other or the
Wily core (which is not true of
Emacs modes). A disadvantage is that
Wily itself cannot directly do subprocess
interaction with ordinary Unix tools at all.
In this and other ways, wily's
distributed scripting is not as powerful as the embedded scripting of
Emacs. The scope of Wily's objectives is correspondingly narrower;
the authors disclaim any interest in syntax-aware editing, or rich
text, for example, and neither Wily nor its Plan 9 ancestor
acme can do these things.
This brings us to another, and sharper way of posing the central
question of this chapter: When do large objectives justify a large
program?