Some of Lisp appreciation is like appreciating the Pixies: it's hard to see what's so amazing about it when so many of its good points have been expropriated by later projects. It's easy to see what's so powerful about Lisp when your only comparison is C, but it's not 1998 anymore, and most new code is written in high-level garbage collected languages.
The rest of Lisp you appreciate by learning to appreciate real macros.
I've read a few books about lisp tininess, beauty etc. I don't know it was late 70s research bias or not. But it seems that no matter how many lisp genes are picked up by other language. Its symbolic/recursive nature can't be matched, and everything else is secondary.
It's the syntax. The elementary nature of s-expressions makes it fairly trivial to mangle them freely, and define all kinds of clever tree structures and so on, all in an easy and consistent way. It also means you never have to remember much symbolic weirdness, you just have to remember (operator atom atoms ...), and that's it.
I think only the functional languages like Haskell and F# come close to the same expressiveness, just from very different angles; auto-currying and lazy evaluation save a lot of boilerplate, and the former is tricky to do in Lisp for precisely one of the reasons it's so flexible (easy variable arguments).
It's a little more than syntax to me, even though it's part of it. Maybe a ~rapprochement between syntax semantics evaluation of recursive domains/categories. The fact that the construction of data through sexps belongs to that same philosophy as valuation between any domains. That the same recursive idea permeates through the whole system.
Lisps do a fantastic job following the principle of least astonishment ( https://en.wikipedia.org/wiki/Principle_of_least_astonishmen... ). I work with Clojure professionally and I find there's a huge advantage to Lisp syntax compared to other languages that I've used. The syntax is very minimal and consistent while also extremely expressive.
Practically all other languages tend provide expressiveness by having tons of syntax sugar. This introduces a lot of special cases, quirks, and rules that you have to keep in your head to work with the language. It's constant mental overhead that distracts me from the problem I'm working on.
A language like Clojure just gets out of your way and lets you focus on your work without having to worry about its quirks all the time. The code tends to work as expected and does what it looks like it's doing on the first try.
The other big advantage is that s-expressions make it much easier to write structural editors like paredit. With Clojure I'm always thinking in terms of manipulating expressions as opposed to lines of code. I think of taking this block of logic and putting it in there and so on.
Finally, the syntax provides additional visual information about the flow of the program. You can tell what parts of code are interacting with one another simply by looking at the nesting. This makes the code much more easily scannable than other languages.
The syntax for function definitions becomes the following:
(fn name? [params* ] condition-map? exprs*)
(fn name? ([params* ] condition-map? exprs*)+)
Read the word syntax? See the EBNF like syntax descriptions with star, question mark and plus syntax operators? See parentheses structure with [] and () in the second example?
star -> zero or more expressions
plus -> one or more expressions
question mark -> one optional expression
That's syntax. It describes the structure of s-expressions which form valid Clojure code.
Not all Clojure s-expressions are valid Clojure code. There are lots of syntactic restrictions. See the syntax for 'fn' above for an example.
> your own private definition notwithstanding.
You may want to take over the Clojure.org site to follow your own, private, definition of Clojure syntax. Personally I prefer the usual definition of Clojure syntax, as for example shown on Clojure.org .
Claiming that Clojure only has s-expressions as syntax is thus wrong and misleading.
Of course Common Liep has s-expressions. Everybody knows that.
But s-expression syntax is not the complete syntax of Lisp programs. Lisp programs are written using s-expressions. But not every s-expression is a valid Lisp program. There is syntax for lambda expressions, various special forms and syntax implemented by macros.
S-expression syntax OTOH is only the syntax for, wait, s-expressions.
A Lisp compiler will check the syntax of Lisp code at compile time:
* (defun foo () (let a ((b 10)) 3))
; in: DEFUN FOO
; (LET A
; ((B 10))
; 3)
;
; caught ERROR:
; Malformed LET bindings: A.
Here the syntax for LET is violated.
The syntax for LET is defined as:
let ({var | (var [init-form])}*) declaration* form* => result*
Thus the compiler expects a list of bindings and not a symbol, as in my example above.
Every Lisp programmer will need to learn this syntax. Not just ATOM plus (S-EXPRESSION*).
I'm not really sure what your point here. These are implementation details of how CL works. Has nothing at all to do with what s-expressions are in principle.
I'm not sure why it's so difficult to understand for you.
S-expressions give us a textual syntax for data. But not for Lisp. Lisp syntax is built on top of s-expressions and has to be learned. That's true for Scheme, too. See chapter 7.1 of the Scheme R7RS specification: "Formal syntax". That's not s-expressions.
> I'm not really sure what your point here.
That seems to be a common theme for you. I'll help you out: Your original points were this:
> Clojure syntax is s-expressions, your own private definition notwithstanding.
Which I showed you by pointing to Clojure.org to be wrong. Clojure syntax is more than s-expressions.
> The syntax is very minimal and consistent while also extremely expressive.
This also is wrong, since the syntax consists of all kinds special forms and macros, too. The user can even introduce lots of new syntax by implementing it as macros. Every library with macros is likely introducing new syntax and semantics.
Isn't it obvious? Reader macros are in the first place there to implement and extend the s-expression reader. It implements parsing of the data structures: conses, lists, arrays, vectors, strings, symbols, various number types, bit vectors, ...
There were three things that really did it for me: powerful macro's + language designed to be easily macro'd; incremental compilation; updating live code. The macro's let me extend it however I liked. OOP was invented? Add a library. Aspects? Add a library. Type-checking? Add a library. In each case, the language itself transforms to more effectively use the approach with much zero-overhead potential given the transforms are usually at compile-time. I had DSL's for common things such as GUI's, state-machines, and databases. Let me do so much with so little work while having consistency across the whole thing.
The second was incremental compilation. I was an imperative programmer and mostly used that style. Languages such as C++ interrupt one's mental flow with long compile cycles. Interactive languages, especially scripting, tended to be too slow in production. LISP let me compile individual functions in a fraction of a second after writing them. Think, type, hit button, examine results, and repeat. The flow stays there and you just keep pumping out more of your solution. That my main DSL was compatible with a C++ subset meant I could just autogenerate that at the end to benefit from its compilers or portability. Otherwise, I stayed in LISP.
The third thing was something that I tried with C/C++. Self-modifying code for AI and security applications was one use. Another was updating apps live. LISP made both pretty easy. The program ran as an instance while you made it in certain development environments. You were in a way always working with a live program. Also, you could drop a member variable in a class definition and all running instances of that class would loose it. Just like that. So easy to change while being able to compile to fast code with type & safety level declarations.
So, it was a language that could do anything I want it to do, allow rapid iterations at max flow, and even change running systems if they weren't good enough. Still haven't felt this full effect in other languages. The old LISP machines went further [1] in that they could take you to any problem in the app or OS right at the source code without crashing. Like LISP apps, you can inspect the issue, modify the code of the running system, optionally modify the instance, and proceed how you wanted to. Still waiting for that feature in Windows or Linux development. ;)
True, I have Scheme, Racket, and Clojure installed on my machine. I tried to get started, but I couldn't think of any simple projects that would accentuate the "uniqueness" of Lisp dialects.
Maybe I'll pick it up again after my love affair with Go dries up.
at one point you write a small evaluator, then extend it through macros. Shows you how to get the linguistic trait you'd like in a few lines. One thing lisp has been used to demonstrate since its birth.
Not sure if games interest you, but "Realm of Racket" is a fun read to get into Racket with. I mostly got into the rest of Lisp because I liked Racket.
Does it have to be unique? A small project that you use now and then that needs an upgrade would probably benefit from being written in lisp (in some manner).
Although I will admit Lisp machines were pretty neat.