Hacker News new | past | comments | ask | show | jobs | submit login
Yes, JavaScript Is a Lisp (raganwald.com)
21 points by Akronymus on Jan 13, 2023 | hide | past | favorite | 21 comments



"But saying that JavaScript is “a” Lisp when talking about JavaScript… That’s a different matter. It makes one pause. The obvious response is to ask “In what way?” This leads to conversations about recursion and trampolining and the use of IIFEs to create block scope."

At which point the author should have SAID something, instead of just being clever.


He had to say something clever because the obvious answer is that JavaScript is not a lisp because it does not use a superabundance of parentheses.


> I do not think it adds value to thinking about Scheme or Lisp to think that there is a meaningful relationship between Scheme or Lisp and JavaScript. but I do think it is interesting and productive to think about what JavaScript draws from Scheme and/or Lisp, and to that end I often say that JavaScript is “a” Lisp when talking about JavaScript.

The summary gets to the point well enough... you'd expect the point to be there rather than in the middle of the post.


Which of these conversation topics relate to Lisp?


It's no accident. If you read any early historical accounts[0] of Mocha -> LiveScript -> JavaScript, you can see why. From the linked:

> What was meant to be a Scheme for the browser turned into something very different. The pressure to close the deal with Sun and make Mocha a scripting companion to Java forced Eich's hand. A Java-like syntax was required, and familiar semantics for many common idioms was also adopted. So Mocha was not like Scheme at all. It looked like a dynamic Java, but underneath it was a very different beast: a premature lovechild of Scheme and Self, with Java looks.

[0] https://auth0.com/blog/a-brief-history-of-javascript/


It's not homoiconic, which makes it not a lisp for me.


That's the most fundamental one, plus the lack of expression based syntax (!), macros, quoting, (truly) interactive development...

It seems to me that there isn't much there outside of proper closures and data literals? Go has those as well and it's hard to compare to a Lisp.

When JS was invented it was inspired by Scheme and apparently having those two features was more out of the ordinary then. But that's hardly true today.


wdym, JS has good interactive development with browser devtools. and you can write pretty much all js as expressions if you want to.


I agree, I write JS professionally.

But there is a _qualitative_ difference between interactive development between JS and a Lisp like Clojure. And "pretty much" is also qualitatively less expressive than "everything is an expression".

I write expression based, simple, mostly functional JS and I use tools to make that as interactive as is practical. But there's still a lot of friction and differences between that and development with a Lisp (in my case Clojure).


That’s just as true of Java nowadays, though. Plenty of languages have REPLs.


The entire Javascript language is not striclty homoiconic, then again many lisps aren't if you're exceedingly hawkish about that term, but the "code is data" mentality is obviously present in the way Javascript and JSON interact.


Our doom was pretty much sealed when Norvig called Python a Lisp. :)

https://norvig.com/python-lisp.html


Absolutely zero information in this article.


I don’t agree. AST-as-syntax (the S-expressions) is the thing that makes a language a Lisp. Everything else (macros/non-typed/GC/Lambdas/REPL/…) is available in other languages.

Note that it is AST-as-syntax that is important. Not the concrete S-expressions syntax. You could use XML or JSON to write the AST-as-syntax instead and you would have a Lisp. However S-expressions are much more concise that XML/JSON.


Lisp code is written in a simpler notation than ASTs, it's written as nested lists of objects.

Hierarchical data-structure: the s-expressions are based on nested lists of arbitrary data. Like symbols, numbers, strings and many others.

Lisp code is then written as s-expressions, given some syntax rules, which s-expressions are valid Lisp expressions.

S-expressions are no abstract syntax trees, since they capture no syntax information about the meaning of the symbols. That gives an extra degree of freedom: the input to the Lisp evaluator is not an AST (either read or generated by a parser), it's just nested lists of symbols and other objects. That means for example that Lisp macros accept code in an arbitrary syntax.

For example this is valid Lisp code when LOOP is a macro and implements its own syntax.

  (loop for i upto n and j in list when (and (= i j) i) sum it)
We could write above as

  (loop for for upto n and in in list when (and (= for in) for) sum it)
and it would still work. The s-expression does not encode basic information like what the meaning of the identifier is. That depends on the macro. An AST would encode what the construct is: operator, variable, class, ... Lisp s-expressions don't do that.

So it's important for Lisp that the code is NOT written as an AST. It's just tree-like -> in the Lisp case it's nested lists of symbols and other atoms.


> Lisp code is written in a simpler notation than ASTs, it's written as nested lists of objects.

That is exactly what a simple AST is: A tree structure representing the semantics of the code you wrote with no syntax info.

> Hierarchical data-structure: the s-expressions are based on nested lists of arbitrary data. Like symbols, numbers, strings and many others.

This is exactly what AST’s do as well. Whether it contains the semantics of Lisp code or (say) C code.

> S-expressions are no abstract syntax trees, since they capture no syntax information about the meaning of the symbols.

This is a misunderstanding. AST’s capture semantics not syntax. You can pretty print AST’s into a different semantically equivalent syntax if you want. That is what some compilers do: Lisp syntax -> AST -> C syntax.

> That gives an extra degree of freedom: the input to the Lisp evaluator is not an AST (either read or generated by a parser), it's just nested lists of symbols and other objects.

This is not correct. The input to Lisp macros are runtime data structures. Not syntax. Different Lisp interpreters/compiles use different runtime representations. Typically linked lists.

The nodes in the linked list typically have a lot of additional information like type information, column/line data for error reporting etc.

> That means for example that Lisp macros accept code in an arbitrary syntax.

Nope. As I said above, macros accept runtime data. You need to clearly separate syntax from semantics/runtime in your head to understand what is really going on.

> For example this is valid Lisp code when LOOP is a macro and implements its own syntax.

Macros are runtime code executed during the parser stage. It has nothing to do with syntax. Other languages execute macros the same way. That’s why I said above that macros aren’t unique to Lisp.

> The s-expression does not encode basic information like what the meaning of the identifier is.

That is correct. You can define different semantics to the same S-expression.

However for the S-expression to be interpreted as Lisp you need to use the Lisp semantics to run it.

I use S-expressions all the time for configuration files etc. However I use a different semantics. So the S-expression might look like Lisp but it isn’t Lisp in those cases.

> An AST would encode what the construct is: operator, variable, class, ... Lisp s-expressions don't do that.

Nope that is not true. A typical AST for Lisp would have the following semantics categories:

List, Symbol, Integer, Float, String.

That’s it. There aren’t many. That’s what makes Lisp so easy to implement. In other languages you might have 20+.

That’s exactly what I mean when I say that the Lisp syntax directly represents the AST. And no other language does. That is the one feature that makes Lisp unique.


Lisp objects are the abstract syntax tree of a data notation. E.g the semantics of (a b 3) is that "I am a list of two symbols and an integer"; and the object which results from that is the abstract syntax which captures that exact structure and meaning: it is a list object, with symbol objects in it, and an integer. That object is that what the S-expressions says it denotes.

There is no code AST there which says "I'm a call to function a with two arguments".

Lisp evaluation/compilation is the imposition of code semantics on an abstract syntax that is concerned only with data. No, not just code semantics, but syntax and semantics. The Lisp code view of data imposes additional syntax (sometimes) which isn't represented.

Macros definitely implement syntax. They have a run-time, but that is not really relevant, except for macros that have weird side effects, or interact with the environment (read from files).

Lisp macros sometimes parse nested syntax beyond just accessing positions in the data; like the example of the loop macro, which recognizes a phrase structure grammar defining the loop clauses.

We can have a macro (infix a + b * c) which performs operator precedence parsing, producing (+ a (* b c)). That macro is doing nothing but providing syntax; it doesn't care what the symbols mean beyond their arity, associativity and precedence.

Some Lisp compilers go straight from the data AST (after macroexpansion) to code. Others build an actual code AST for doing multiple passes. There may be nodes in there like function-call and variable-binding and whatnot. CLOS objects could be used, etc.

This is the key insight: the AST concept can exist in more than one way in the same situation involving the same pieces. A S-expression denotes data. That data is its AST. That AST potentially denotes code. But not as abstract syntax: the S-expression's AST is abstract toward the S-expression, but serves as entirely concrete syntax for code. The concrete syntax has some of the benefits of an AST, because it exhibits nesting, and can eliminate the need for a secondary AST.

Think about a Common Lisp function definition with optional arguments (defun foo (a &optional b c)). That data structure is not abstract syntax: it includes the concrete punctuator &optional.

Abstract syntax needs an abstraction like a "function-definition" object, which has "optional-arguments" as a property --- and no sight of any &optional punctuator any more. To get there, we must parse the function definition; scanning the parameters for the presence of &optional, and &rest, &key, &aux.

For us to have a code AST node, the &optional word must be gone, for exactly the same reason that parentheses are gone from the S-expression when it becomes a data AST.


well written


> That is exactly what a simple AST is: A tree structure representing the semantics of the code you wrote with no syntax info.

That what I say: the s-expression does not represent the semantics. It does not know much about Lisp: no syntax and no semantics. It mainly uses similar data structures for the code: lists, symbols, numbers, etc.

See this for an AST: https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/Ab...

In Lisp the s-expression does not say that 'a' is a variable name. We don't know that '>' is a compare op.

(+ 1 a) would be written as an AST maybe like:

(function-call :operator (built-in-variadic-function cl:+) :arguments ((numeric-constant 1) (variable a)))

> As I said above, macros accept runtime data.

No, macros are also expanded before runtime.

> Macros are runtime code executed during the parser stage.

The s-expression is unparsed. The macro has to do the parsing.

> Other languages execute macros the same way.

No, in most languages macros get an AST, where syntax is represented and pre-parsed. For example the macro gets pre-parsed code, where the syntactic categories are determined. + is an operator. In Lisp + is just a symbol and that is no specific syntactic category. Thus most macro systems in languages, the code is pre-parsed and the result is given to the macro expansion. That means a use of the identifier + is an infix function. Not so in Lisp. + is just a symbol and we don't know yet what it is: data, operator, variable, lexical variable, etc.

> You can define different semantics to the same S-expression.

And also syntax. (+ + +) -> is that postfix, prefix or infix? Lisp does have prefix syntax, but the macro can assume anything.

> However for the S-expression to be interpreted as Lisp you need to use the Lisp semantics to run it.

And the syntax.

> A typical AST for Lisp would have the following semantics categories: List, Symbol, Integer, Float, String.

That does not describe Lisp, that describes S-expressions. S-expressions are not Lisp, they are just a syntax for data. The Lisp program has simply a different notation in the form of s-expressions, but no syntactically or semantically representation. An AST would be already representing syntactical information like the syntactical categories of the identifiers present.

1 + 1 is just another notation, like (+ 1 1) is another one. It's not yet the a new quality. The main difference is that in the case of Lisp, s-expressions are backed by a simple data structure, nested lists, which provides some hierarchy, but no representation of syntax. I would think of it as a token tree output of some kind of lexer.

If I have (let ((a (+ b 10))) (* a b)), then in Lisp LET is a special operator, A is a lexical variable, b is a variable, 10 is an integer constant, * is a built-in function, etc.

The syntax for above LET is:

  let ({var | (var [init-form])}*) declaration* form*
> That’s exactly what I mean when I say that the Lisp syntax directly represents the AST. And no other language does. That is the one feature that makes Lisp unique.

Yeah, but that's wrong. The Lisp syntax is on top of s-expressions. You seem to believe that s-expressions represent Lisp syntax. They can't s-expressions is a format to represent data, not a specific programming language. Thus they have no idea of syntactic categories or anything like the information which would be in an Abstract Syntax Tree.


Have people forgotten The Little JavaScripter?

"Because of this deep similarity, all of the functions in The Little Schemer can be written in JavaScript."

https://www.crockford.com/little.html

(HN readers may also appreciate the JavaScript Y combinator.)


From 2013




Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: