Flavors wasn’t just another Lisp library. When it emerged on the MIT Lisp machines in the early 1980s, get redirected here it bent the way programmers thought about structuring complex systems. Over forty years later, its fingerprints are still visible in everything from the Common Lisp Object System to modern mixin-based frameworks. To understand why Flavors matters, you have to look past syntax and see it as a philosophy of extensibility—one that treated object-oriented programming as a toolkit for composing behavior, not just inheriting it.
The Lisp Machine Crucible
Before Flavors, Lisp already had a tradition of building ad‑hoc object systems: nested closures could hold state, property lists could tag symbols, and macros could wrap code into message-passing idioms. But the Lisp Machine environment demanded something more coherent. Operating systems, window managers, file systems, and user interfaces were being written entirely in Lisp, often by small teams that needed to share and extend code continuously. The sheer complexity of the software stack meant that monolithic class hierarchies and single-inheritance schemes broke down quickly. Multiple inheritance was necessary, but the real breakthrough was understanding how methods from different ancestors should combine, not just which one won.
Howard Cannon and David A. Moon, working at the MIT Artificial Intelligence Laboratory, shaped Flavors between 1980 and 1982. The name was deliberately playful: just as ice cream gets new flavors by mixing a base with enhancements, a software flavor mixed base functionality with optional behaviors. Though humorous, the metaphor encoded a powerful design principle. Flavor composition wasn’t about “is‑a” taxonomy; it was about “has‑the‑properties‑of” and “does‑this‑in‑addition.” The system became integral to Symbolics’ ZetaLisp and later to Lisp Machines from LMI and Texas Instruments. For a time, it was the most widely used object-oriented extension in the Lisp world.
Flavors, Mixins, and the Decomposition of Behavior
A flavor definition looked deceptively simple. Using defflavor, you named the new flavor, listed its instance variables, and then listed the flavors it depended on—a bit like specifying a class and its superclasses. For example:
lisp
(defflavor bordered-window (border-width) (window border-mixin) :settable-instance-variables)
Here bordered-window inherits from both window and border-mixin. The order mattered, but not solely for conflict resolution in the style of “linearization.” Flavors introduced the idea that a flavor is made by mixing components, and methods from different components can be combined rather than simply overridden.
The critical innovation was method combination. A generic operation, invoked by sending a message to an instance with send, could trigger a cascade of methods. Instead of a single most-specific method executing and possibly calling call-next-method, Flavors defined a taxonomy of method roles:
- Primary methods carried out the main task.
- Before demons (
:beforemethods) ran automatically before any primary method. - After demons (
:aftermethods) ran after the primary method. - Around wrappers (
:aroundmethods) wrapped the entire computation, controlling whether and how inner methods executed.
All of this happened without explicit call-next-method chains inside every method. The system itself orchestrated the combination, following the flavor precedence order. If you added a log-mixin that defined an :after method for :redisplay, every flavor mixing it in would automatically log after redisplay, with zero change to the primary redisplay logic. This declarative separation of concerns was startlingly productive. You could build an application by writing independent mixins for logging, border drawing, mouse sensitivity, and save/restore, then combining them into concrete flavors. Reuse skyrocketed because each mixin captured a single, orthogonal responsibility that could be combined with many base flavors.
Precedence and the Problem of Order
Multiple inheritance always raises the question: if several components provide a method for the same message, which one runs? Flavors answered with a flavor precedence list built depth‑first, left‑to‑right, with duplication elimination. The algorithm was simple but sometimes counter‑intuitive, especially when mixing deeply nested components. Later, the Lisp community refined this into the C3 linearization used by CLOS and languages like Python and Dylan, but Flavors’ original approach was a practical starting point that got thousands of programmers thinking about method resolution order.
More importantly, Flavors’ method‑role semantics often reduced the pain of ordering. :before and :after methods from all applicable flavors would run, not just the most specific one. The primary method came only from the most specific flavor that provided one. This meant you didn’t need to worry about which mixin’s :after ran first as much, because they all contributed. Conflicts were usually over primary methods, and those were resolved by the linearization. When you did need fine control, :around methods let a particular component wrap the entire combined method, effectively overriding everything inside.
Internal Machinery and the Message-Passing Model
Flavors was not just an academic exercise. The implementation had to run efficiently on stock hardware of the early 1980s. Flavor instances were typically structures or arrays, with instance variables accessed by slot offset. Method dispatch was handled by sending a message through send, which queried the instance’s flavor for a compiled dispatch table. The system pre‑computed combined methods—called “generic functions” in later parlance—at flavor definition time. When you called send instance :move-to x y, the runtime looked up the pre‑combined method and jumped to it. There was no sophisticated cache or dynamic method generation during call; the intelligence was in the compile‑time combiner, which wove together primary, before, after, and around code into a single function.
This architecture made Flavors fast enough for interactive graphics and operating‑system services. Symbolics’ Dynamic Windows system, its file browser, the Zmacs editor, and the legendary Document Examiner all leaned heavily on Flavors. Windowing systems, in particular, benefited from the mixin style: a pane could be a basic-pane mixed with scroll-mixin, process-mixin, and border-mixin, each contributing its own methods that combined seamlessly.
The Leap from Flavors to CLOS
By the mid‑1980s, the Lisp community was standardizing Common Lisp. The object system debate—often called the “Lisp object‑system wars”—pitted Flavors, Loops, CommonLoops, and other proposals against one another. Flavors contributed several foundational ideas that made it into the Common Lisp Object System (CLOS), finalized in 1988 and incorporated into the ANSI Common Lisp standard.
- Method combination in CLOS is a direct descendant. CLOS kept the
:before,:after, and:aroundqualifiers, and extended the concept with user‑definable method combination types (progn, and, or, append, etc.). That whole apparatus traces back to Flavors. - Mixins as a style of design became an official term in CLOS, though Flavors arguably originated the concept. In CLOS, you don’t define a “flavor,” you define classes and then combine superclasses, but the spirit of mixing independent reusable pieces remains.
- Generic functions and multi‑methods replaced the message‑passing model. Instead of
send instance operation args, CLOS defines generic functions with methods specialized on any argument, not just the first. Flavors had only single dispatch (the first argument, the instance), so multi‑methods were a generalization, but the core idea of a generic operation that combines methods via qualifiers came from Flavors. - The Metaobject Protocol, which exposed introspection and intercession to the programmer, wasn’t in Flavors, but Flavors’ practice of pre‑computing combined methods and making flavor definitions first‑class objects paved the way for a reflective object system.
The Legacy Beyond Lisp
Though Flavors itself faded as Lisp Machines declined, its ideas permeated modern programming. Mixin‑based design, once exotic, is now a staple in JavaScript classes, Python’s multiple inheritance, Scala traits, and Ruby modules. The insight that cross‑cutting behavior can be layered via before/after hooks without modifying core logic underlies both Aspect‑Oriented Programming and the decorator pattern. Python class decorators and JavaScript Proxy objects can be seen as distant echoes of :around wrappers.
At a deeper level, Flavors taught a generation that object‑oriented programming is not only about classification and hierarchy. It can be about combining largely independent capabilities into a coherent whole. The flavor metaphor, as whimsical as it sounds, captures something essential: you don’t have to force your domain model into a rigid taxonomic tree. You can mix a little scrolling, a little input handling, a little persistent state, and end up with exactly the artifact you need.
A Snapshot That Still Instructs
For those who read old Lisp Machine manuals, there’s a moment of revelation when you see how a few dozen mixins replace what would today be a sprawling component library. A Symbolics programmer didn’t hunt for the exact widget subclass; they built it out of pieces. That compositional mindset is precisely what many modern frameworks, from React to Rust’s trait system, are trying to recapture.
Flavors was an early, complete demonstration that an object‑oriented extension doesn’t need to mimic Smalltalk. It can emerge naturally from Lisp’s symbolic and functional traditions, offering a distinct take on objects that prioritizes protocol combination over message‑passing orthodoxy. The system not only supported the intense demands of Lisp Machine development but also shaped the design of CLOS, which remains one of the most powerful object systems ever created. The thousand‑word journey through Flavors ends where it began: with the understanding that extensibility, when done right, find out feels less like engineering and more like mixing the perfect batch of ice cream.