Lisp generally has precise GC, which I'd say makes it light side. It's even relatively type-safe if you count runtime type-checking.

Highly reliable systems are written in Erlang, which if you squint is another Lisp dialect. There's even a sexp-based version called LFE, for Lisp-flavored Erlang. Erlang's key to reliability is error recovery, rather than exceptional levels of error prevention.

I do like your light side/dark side classification.

> Erlang, which if you squint is another Lisp dialect

Prolog disagrees. IIRC the first versions of Erlang were written in Prolog, and you can still see its influence in the syntax.

Correct, although I would assert Prolog shares many concepts with Lisp.

If you squint enough, JS is a lisp..

Define a goddamn language, syntax is not enough to define one! What are the semantics? Without that you are just talking about syntax trees like they would mean anything

never understood why people say that: the syntax for defining code seems quite different from the syntax defining data structure. There's no homoiconicity in javascript..

And that's just syntax, it doesn't give you a programming language at all.

JS is a dynamically typed language with prototypical inheritance objects that work like universal key-value maps for the most part. It is also mutable.

Clojure is a dynamically typed language with key-value maps. It is also immutable.

You can surely see where I'm going , the underlying semantic model is the meaningful part. Homoiconicity doesn't give you anything special if your language can parse itself and can eval code. It just makes these completely abstract implementations simpler.

> Homoiconicity doesn't give you anything special if your language can parse itself and can eval code. It just makes these completely abstract implementations simpler.

Well, in a way it does give you something: By making expression of things like macros simpler, it makes them sometimes worthwhile, and makes it a reasonable request to have this kind of meta programming in your language at all. Without homoiconicity such things become even more difficult endeavors and often unjustifiable for the language design and its implementation.

I mentioned in other comment that homoiconicity doesn't necessarily make writing macros simpler. It makes writing trivial toy examples simpler.

But let's compare it to a modern macro system like rust's or scala's, where you get a typed object representation of the AST, and for anything non-trivial you are better off with this latter.

Also, arguably the best is to have certain features in the language itself, that can be used to build proper abstractions - so you don't have to resolve to using macros in its place.

> Also, arguably the best is to have certain features in the language itself, that can be used to build proper abstractions - so you don't have to resolve to using macros in its place.

I don't agree, because that would mean, that the language must be huge, or grow huge over time, or alternatively be extremely abstract at its core, to be able to fit every use-case. Furthermore, so far I have not seen a language, in which the language designers managed to pull it off, so I tend to think that what already has been successfully pulled off, which is macros for language extensions, is the way to go.

Also this seems to be arguing from a limiting idea about what macros do. Macros are not always the right solution for any problem, in fact often they are not, but there are things you simply cannot do otherwise (without syntactic clutter), like for example changing the order of evaluation.

I also don't agree with your point about only making toy examples easier to write. For example I have written macros for implementing new define forms, which allow to specify contracts for function arguments and return values, or a macro for automatically defining functions that communicate to API routes, based on the function name, which I used to implement a proof of concept docker client for Scheme. Those are not toy examples, but real world applications, where a small macro can have big effect.

If you think macros need to be big and elaborate and complicated and otherwise are toys, then you don't really understand the power of macros. One of my favorite macros is the following threading macro:

    (define-syntax ->
      (syntax-rules ()
        [(-> expr) expr]
        [(-> expr* ... (op args* ...))
         (op args* ... (-> expr* ...))]
        [(-> expr* ... op)
         (op (-> expr* ...))]))
Small, but a great addition to the code, that improves readability in many places of the code. It doesn't have to be big or long, in order to not be a toy example, but actually be a useful macro.

Well, my main point is not to never use macros, but that in certain cases a language-native feature that can also solve a problem you would use a macro for, it's probably better (better debugging, error messages, etc).

And my other point was macro systems in other languages, which I believe are better, in part due to not having homoiconicity, like Scala or rust.

> If you squint enough, JS is a lisp..

Yes, that is true. I'm not big on the idea that Lisp is defined by parentheses. The implementation strategies are another way to look at it. That doesn't capture it either, but it's an angle to try.

So then what it is? Because otherwise it's a magical nothing-term to which everything lisp people like applies, but no criticism can ever reach it because "that's not really lisp, see it's different in this other implementation"

In my mind, it's 2 distinct criteria: interactivity and the manipulation of symbols (whether those are implemented as symbols or identifiers doesn't really matter). I don't know about Erlang, but from the admittedly little JS I've written, I believe it achieves both criteria, even if it's worse at them than a "proper" Lisp.

It isn't, Dylan and Julia are two Lisps with Algol like syntax.

Indeed there was a proposal to add Algol like syntax to Lisp,

https://en.wikipedia.org/wiki/LISP_2

JS is Self, which is a dialect of Smalltalk. So not really Lisp imo