Lisp didn’t fail. There are several successful modern Lisps, like Clojure.

But, moreover†, there are also successful modern languages that don’t look like Lisps at first glance, but which certainly are Lisps by most definitions—the best example I know of being Elixir.

Rather than a distinct compiler that does codegen to a distinct architecture target, languages like Elixir (and Clojure!) consist of two distinct components:

1. A plain language grammar parser, which parses “syntactic literals” of the language into a Lisp-alike AST (and Lisp had one of these as well, even in its first incarnation—Lisp’s “syntactic literals” were called https://en.m.wikipedia.org/wiki/M-expression s.)

2. A homoiconic runtime interpreter of this AST, with hygenic macro support, which evaluates macros at runtime with the side-effect of producing a program. This “runtime” is called “compile time”, but it’s really just the same runtime that the compiled program runs in (and both have equal access to the machinery that produces programs.)

In any Lisp, the definition of a function or a module isn’t a special form that the compiler does something with; rather, `defn`-like clauses are just references to an ordinary macro, which the S-expression interpreter invokes when attempting to reduce the AST that has `defn` as its head. The `defn` macro consumes a parameter list and an expression body, and has the side effect of producing a compiled function in some way; and then the `defclass`-alike macro has the side-effect of collecting those compiled functions generated within its scope, and producing a compiled module from them.

And, in any Lisp, the M-expressions (like those of Clojure, or those of Elixir) are a convenient syntax to write code literals in, but you don’t have to use them; you can just as well write “raw AST” as the M-expression representation of the AST’s data-structure literals (since, in a Lisp, these data-structures are always ordinary stdlib data structures like lists or tuples, rather than compiler-specific data-structures); or, equally well, you can generate and build these AST data structures by writing functions to produce them, and then call these functions inside a macro body in place of where you’d write a quoted M-expression literal or a plain S-expression ADT literal. In est, there’s no difference between the macros that a user of such a language writes, and the macros that define the language; both are just homoiconic AST->AST mapping functions. There are a few that do fancy side-effects involving invoking an object-code compiler... but you can write your own functions that do that as well. (This is why it’s so easy to build a new language [like HN’s runtime Arc language] on top of an existing Lisp—there’s nothing stopping you from writing macros that call out to your own codegen machinery, and at that point you’ve bootstrapped your way out of the original language.)

——

† Note that, by this definition, there really isn’t any difference between Clojure and Elixir; I didn’t need to distinguish “actual Lisps” and “languages that act like Lisps.” Clojure and Elixir are both Lisps—both M-expression glosses on top of a “compile time”-runtime S-expression evaluation engine with compilation as a side-effect of certain “compile time”-runtime macro invocations.

Certainly, Elixir has special M-expression syntax for calling various macros (`if`, `for`, etc.), but adding such a thing to Clojure wouldn’t make it any less Clojure.

The real contrast, if there is one, is that Elixir has no homoiconic M-expression syntax for S-expressions (i.e. no equivalent to Lisp’s parentheses), so you can’t just write a low-level S-expression equivalent of an M-expression inline in a function in order to have that AST subtree be part of the function. Instead, you have to define a separate macro to declare your intent to inject a low-level S-expression, and call it at that point in the function body; and you have to write such ASTs in an alternate M-expression form (as tuple data-structure literals.)

This is less of a distinction between the languages as languages, though, and more a distinction between the languages as M-expression syntaxes. You could define an alternate M-expression syntax for Elixir, which did have direct inline S-expression support, and the resulting language—at least in my opinion—would still be Elixir.