An Intuition for Lisp Syntax
stopa.io
stopa.io
Not me. I used C before Lisp, and Pascal before that. C replaced syntax like BEGIN and END and "END <procedure name>" with just { }. I thought this was a most excellent thing.
I was still not using Lisp when HTML and then XML came along. My reaction to XML was this: since most of the content of XML is payload data, why don't we just fscking use parentheses or braces to structure it? Some {foo bar {baz}} or whatever instead of this moronic, bandwidth-wasting, unreadable nonsense <foo>...</foo>.
I could understand it when the markup is just a few raisins in the pudding like:
> Lorem ipsum dolor sit amet, <i>consectetur</i> adipiscing elit, sed do eiusmod
but this XML thing made no sense as a data notation in which every little leaf element is wrapped in verbiage.
So then when I got into Lisp, it basically had the syntax I already wanted; it was "pre-approved".
I already liked parentheses because they occur in English prose (like this), in mathematics, and in almost every programming language I had ever used.
The Unix shell and other command languages eliminate the commas: we don't write
tar, czvf, foo.tar.gz, foo
Moreover, command arguments are not restricted to narrow lexical categories like "must begin with letter or underscore ..."; they are just clumps of non-whitespace characters. If you know any command languages, what Lisp is doing in that regard is obvious; you're not confused by a+b just being an argument, different from a + b.Commands with the main function on the left followed by space-separated arguments occur in parentheses in POSIX command substitution syntax:
printf "[%s]\n" "$(cat file1 file2)" > file3
dironly=$(basename $(dirname a/b/c))
In general, by the time I got into Lisp I had written so many scanners and parsers, solved so many shift-reduce and reduce-reduce conflicts and whatnot, I knew a good thing when I saw it.Ansible really should have ditched the YAML format a long time ago. The YAML is practically designed to be executable; I'm almost surprised that you can't add a shebang line pointing to Ansible at the top of your YAML. I'm actually a little surprised they've never offered an option to write parts of your Ansible setup in Python.
I would love to make some of my roles and my playbook into Python code. I am forever googling the syntax to set up a loop in Ansible YAML, as well as how to make dependencies between roles, which is pretty cleanly solved with Python modules (just import and execute the role at the top of your new role).
You actually can do that, with something like:
#!/usr/bin/env ansible-playbook
---
- hosts: localhost
tasks:
- debug: var=ansible_distribution_release
It works pretty nicely, just remember to make your playbook file executable. It's a bit cumbersome though since you cannot use `ansible-playbook` arguments like `--limit`, `--diff`, `--check` and so on to have better control over the playbook execution. [](){}
is valid syntax for a lambda. (I think in C++20 you can also stick in < > somewhere in between.)Lisp's S-expressions are just acquired taste (like coffee of a particular blend).
[]{}
[]{}()
[](){}()Of course it is this very fact that makes code and data interchangeable in Lisp and allows for constructs that other programming languages with syntax can't mimic (e.g., powerful macro features).
So this gives good opportunity to Editor makers. Compile "this" level, 1-level-up, 2-level-up and so on. In an editor you can always check your program while coding.
Example is Cider package for Emacs for Clojure programming language.
This is a complete Scheme program:
(cond
[(= 1 0) (error "should be unreachabe")]
(else "should always be reached"))
However this: (else "should always be reached")
Is not a Scheme program, unless the binding `else` be defined in the current scope..But, we can of course go even further than that:
(library (main constants (0 0 1))
(export pi)
(define pi 3.1415926535)
)
`(0 0 1)` is not a valid Scheme program, no matter what one define at any point; an integer can never be the operand of an expression.The Lisp syntax then is defined for these token trees.
For example the syntax for an argument list in Common Lisp is defined with an EBNF syntax description form:
lambda-list::= (var*
[&optional {var | (var [init-form [supplied-p-parameter]])}*]
[&rest var]
[&key {var | ({var | (keyword-name var)} [init-form [supplied-p-parameter]])}* [&allow-other-keys]]
[&aux {var | (var [init-form])}*])It's not even only about the syntax. The Python environment, as far as I understand (and please correct me if I'm wrong), does not support re-definitions very well. Some things can be re-defined, but some cannot. So that's a problem in itself which makes REPL-driven development not very useful in Python.
The benefit of easy AST manipulation just doesn't seem worth it when the cost is a very heavy and verbose syntax. Everything just takes a little longer to write in Lisp, and the syntax is far too noisy.
Moreover, languages with powerful and easy to use macros (like Julia) don't have a paren heavy syntax, so what value does Lisp syntax really even provide anymore? New languages have demonstrated you don't have to sacrifice a lightweight syntax and expressive macros.
It also makes writing any sort of code that uses equations incredibly burdensome to write.
In short, while Lisp syntax used to serve a purpose (facilitation of macros/modification of AST), I feel like it no longer really does.
For comparison, I think OCaml syntax is really nice: light and readable.
Missing obligatory preface of "unpopular opinion".
Wat.
"Very heavy and verbose syntax"? Are you even sure you are talking about lisp?
After you understand how Lisp works, syntax and "features" in other languages feel pretty awkward, to put it mildly.
It's the fact that lisp style is heavily oriented around expressions (since almost everything is an expression) which leads to a lot of nesting and a functional style, rather than a block-oriented imperative style.
So it doesn't stand out as much on its own, and either you give up or by the time you make it through enough of the other hurdles you probably appreciate the expression-orientedness.
I say this as a Haskell enthusiast.
Do-notation probably also masks the fact that everything is an expression from newcomers a bit in the beginning.
Why are we so hung up on the superficial details of the syntax. Can't we just have an AST spec with bijective mappings between different human presentations? Editors already do color coding for us and people use different fonts for their code, but flamewars about what colors and fonts are quite rare. I don't see this as anything fundamentally different.
Is it somehow difficult to accept that different syntax may map to identical AST? Lisp is intentionally syntactically very close to its AST and I find this elegant. But why are we so hung up that the human interface for this has to be parens? Are people confused somehow that wanting to use different syntax means they want to change the language? Even if it no way forces people not to use parens if they want. Can't "the language" just be the AST?
Am I overlooking something? I find this to be an almost trivial solution to many many neverending flamewars. I personally don't like parens because I want computer to do what it does best, which is doing routine churn like matching up parens. I don't mind if somebody else wants to see the parens. Why should I mind? The computer doesn't care at all. Multiple people could edit the very same code, one with parens, one with brackets and one without either (e.g. significant indentation).
The EMACS solution is paredit or something where the editor tracks the parens. But why do these have to be there at all if the computer already knows how to track them?
For me this is almost identical issue to C-family semicolons, tabs-vs-spaces indentation, formatting guidelines, the Guido colon, significant whitespace etc. Is this some authority or status or tribal thing or something? Or cultural lag from moveable type era?
I don't get it. Why people find it so important how other people use want to see the superficial syntax? It's like having strong opinions how other people should paint the interiors of their house to me. Or is there some mix-up between form and function and we have different understandings where the separation goes?
BTW, I recall seeing this post before, but it's not dated. As per wayback machine this has been published in 2020, and seemingly just has "repost=true" GET-variable for "dupe-busting(?)". Shouldn't this be marked as (2020) in the title?
Contrast this to Python. The best feature is f-strings, which approaches the ease of backtick/comma. But the lack of homoiconicity means you can't just jam a bunch of statements in a list -- for crap's sake, indentation makes everything a pain.
I fail to see the connection with f-strings to lack of homoiconicity, let alone indentation. I'm also a bit amazed why people see indentation as painful, don't you indent your code if the indentation is not significant?
Well, not you; your editor does it for you.
That being said, it's still an open problem to develop an ergonomic AST editor... I do want to see it.
Or, more recently, hazel.
Thank you for these examples. There is also Anarres, a structured editor for Fennel (which is Clojure-like for Lua). Anyone knows of more?
https://interlisp.org/ shouldn't be hard
> more
https://github.com/disconcision/fructure
Partly inspired by hazel, less active but probably a more broadly appealing language.
What I like about Lisp's syntax is that the cursor (point) is always in a complete Lisp program. You move up one set of parenthesis, again a complete program. Move up further .... till you reach the top (file-level).
So this gives good opportunity to Editor makers. Compile "this" level, 1-level-up, 2-level-up and so on. In an editor you can always check your program while coding.You get the benefits of writing code composed of neat delineated expressions that nest arbitrarily but without the verbosity that parentheses add to the source file.
Most Lisps have syntax anyways, in order to make the language less verbose.
There's a reason people like syntax, because it is expressive.
What is the value of this statement?
3 + 100 % 2 / 5
I’d like to have added a power calculation somewhere in there for illustration, but most general-purpose languages make that a function. It’s worth asking whether they ran out of infix symbols/syntax or chose a function for some other reason. (map - (quote (1 2 3)))
I'm only talking about cases where the first thing isn't callable, but the second thing is: (3 + 1)
It seems like this couldn't ever be a problem, because the only cases in which the infix strategy would be used are cases which would have been invalid programs anyway. Does that make sense?Though personally I don't particularly find (+ 1 2 3 4 5) less readable than 1+2+3+4+5, and since most of my programs don't have math expressions much more complicated than that, even without cmu-infix or alternatives (Maxima is great when you need to do real math, and for other related things I might as well link https://github.com/CodyReichert/awesome-cl#numerical-and-sci...) I'd find the rest of the tradeoffs worth it, much like once I thought despite Python not having i++ or ++i it was still worthwhile. (In Lisp, by the way, one would use (incf i).)
There are also many people who like the imperial system more than the metric system.
Given that new languages with such syntax continue to be developed and that many express that they favor it, it is at best a personal taste, and at worst simply inertia.
“conventional mathematical notation” was never designed; much like the imperial system, it organically grew and I find it somewhat arbitrary what operations receive an infix operation and what do not and it even depends on the language in some cases.
That being said, I really do not favor `string-append` where `strapp` suffice. I especially do not favor `call-with-current-continuation` over `call/cc`.
False. All you really need is a bijection: McCarthy himself suggested f[x;y]<=>(f x y) and it seems reasonable (and it turns out to be actually useful!) to declare a domain of f where you permit xfy<=>(f x y)
One of his students implemented this: http://i.stanford.edu/pub/cstr/reports/cs/tr/68/92/CS-TR-68-... which certainly has... other issues, but I don't agree infix syntax is incompatible with lisp.
> operator precedence, readable equations, etc.
Yes please.
"readability" is usually given to mean "most people believe they can read it" and not something useful like "most people understand it fully", and nearly any reduction in typing that operator-precedence can offer can be obtained with a simpler rule (like right-of-left) and ordering.
That is to say these things (in their usual meaning) have net-negative value to programs and programmers, are a frequent root-cause of bugs.
Also with regards to prefix notation being unintuitive: We already teach something very similar to schoolchildren learning arithmetic:
1
+ 2
---
Here, as with (+ 1 2), the operator is on the left most side. The operands are arranged horizontally in lisp instead of vertically, but the supposed weirdness of the operator being on the left doesn't seem to bother people when it comes to arithmetic. 1
+ 2
---
as [1 2 +]. I guess it's a difference in perception.For someone who started learning Lisp way way late in my programming journey, I would not have appreciated it had I not went through half dozen other languages before. The sheer simplicity of the foundational concept is liberating.
That simplicity means Lisp actively encojrages exploration (which is why you see so many Lisp dialects). Unfortunately, most people don't really learn anything new unless they were forced into it. Most people, aren't into exploration. They treat language as a short-term tool to get paycheck, and anything making them use more braincells, even to their own long-term benefit, is an annoyance.
To each their own, its just that as good as Lisp is, its MO don't map well with that of most populace.
Not an unjustified one, in fairness. Lisp tends to prefer long, descriptive names.
I've thought about how the verbosity can be avoided, but at certain level of complexity, it is better to have long descriptive names, but I'm not experienced enough with big projects to propose a solution.
in lambdatalk (http://lambdaway.free.fr) one could go beyond and mix html/css using the same syntax, for instance
{div {@ style="color:red"} the hypotenuse of a square triangle (3,4) is equal to {sqrt {+ {* 3 3} {* 4 4}}} }
which can be read like this « write in a div html element, whose style attribute is color red, the hypotenuse of a square triangle (3,4) is equal to the square root of the sum of the product of 3 by 3 and 4 by 4 »
is displayed as « the hypotenuse of a square triangle (3,4) is equal to 5 »
In fact prefixed parenthesis expressions follow the way we think and speak.
(√ as sqrt is not generally primitive, though it can be trivially implemented.)
3
+ 4
- 5
---
2
which was read as "3 + 4 - 5 = 2"I'm not sure I agree. I would argue that people read your example left to right, top to bottom, which would be "1 + 2". At least for people that read their native language this way. Maybe people who read from right to left would read "1 2 +" and be predisposed to Forth?
Edit: To answer to the responses to this comment, it's not that it's conceptually hard, it's just that the parenthesis symbol in its usual incarnation does not work that way, which leads to confusion.
phi is an empty set,
(phi) is a set containing an empty set
((phi)) is a set containing a set containing an empty set
Furthermore, it's one of the only syntax rules; if it's not a list, then the first element is an operator/function.
I have nothing against it, I just think it should be emphasized more.
Edit: Apparently there's alternative syntax for Scheme called wisp.
The only significance whitespace should have is its presence or absence, beyong that, its a pain for human readability.
For me brackets should mostly serve a decorative function and rarely ever be nested or span multiple lines or screens.
In the spirit of compromise I'd like to see text ediors where bracekts or no brackets is just a visual toggle you can switch back and forth on a whim.
When I'm reading or writing code for Java,or C I am reading or writing code. My brain parses all the visible characters and safely ignores the invisible whitespace. Indentation is just better readability.
When I'm reading or writing Python/Yaml, suddenly I'm having to pay attention to what is not visible to my eye as well. Its extra cognitive load and that reduces readability.
Do you ever make the infamous semicolon-after-if-clause bug in C? I do sometimes and it can take ages just to see that semicolon.
I wonder if the same constructs can be made in terms of the other language.
The advantage of infix is that you don't need to know the argument counts for functions/macros to reconstruct the tree, but the disadvantage is extra parens.
I am slowly coming to (subjective) conclusion that postfix is the most natural, because it's the same as the (typical) evaluation order.
What about laziness, which disrupts this apparent order and in fact evaluates from the outside in?
With respect, no. Lisp is by default prefix (Polish notation) but with some work you can make it behave as infix or postfix.
Haskell is naturally either prefix or infix depending on how you define and call your functions (the use of backticks or parens around the function name in a call changes it from prefix to infix and vice-versa. It's a rather elegant solution to the problem.)
As you stated Forth is postfix (Reverse Polish notation).
I haven't tried the reverse (building a Lisp in Forth) but I imagine it would be straightforward. One can build a Lisp from just about anything.
Generalized by replacing "C or Fortran" with a language where the user wants more power over it's syntax and semantics, to witness the author pull it off in just 44 lines of JavaScript was a joy.
Of course some (emacs) omit the initial steps and jump right to the steady state.