[HN Gopher] An Intuition for Lisp Syntax
       ___________________________________________________________________
        
       An Intuition for Lisp Syntax
        
       Author : cercatrova
       Score  : 70 points
       Date   : 2022-08-28 18:24 UTC (4 hours ago)
        
 (HTM) web link (stopa.io)
 (TXT) w3m dump (stopa.io)
        
       | behnamoh wrote:
       | I was personally blown away when I read PG's summary of
       | McCarthy's paper where he came up with a few primitives and built
       | literally any function based on them, including the `eval'
       | function itself. All of this based on some really basic
       | primitives like `atom' and `lambda'.
       | 
       | If writing `eval' is possible in the same language you started
       | with, then you must be able to write your own eval, no? Or
       | redefine the primitives? But then what happens if you
       | accidentally set DEFINE to be 5? Restart the buffer? Is there any
       | way to get back to your original definition of DEFINE after
       | corrupting it?
        
         | mtlmtlmtlmtl wrote:
         | I was gonna respond that you can't redefine the primitives
         | because they're not functions or macros but special forms. And
         | this seems true in Common Lisp. I can't define a function named
         | SETQ(the primitive for assignment in CL, also called set! in
         | other lisps) because it's a reserved name. But then I thought
         | I'd try it in Guile and there I was allowed to (define define
         | 5). Even though define is also a special form in Guile. Which
         | made me unable to define further things, telling me 5 is not a
         | valid argument to APPLY. And no I don't know how to fix it...
        
           | lispm wrote:
           | In Common Lisp you can have namespaces, where you can have
           | your own SETQ:                 CL-USER 53 > (defpackage
           | mylisp                      (:use "COMMON-LISP")    ;
           | everything from Common Lisp                      (:shadow
           | CL:SETQ))      ; but not SETQ       #<The MYLISP package,
           | 1/16 internal, 0/16 external>            CL-USER 54 > (in-
           | package "MYLISP")       #<The MYLISP package, 1/16 internal,
           | 0/16 external>            MYLISP 55 > (defun setq (a b c)
           | (print (list a b c)))       SETQ            MYLISP 56 > (setq
           | (+ 1 2) 4 5)            (3 4 5)        (3 4 5)
           | MYLISP 57 > (cl:setq a 12)       12
           | 
           | Most implementations also have a way to protect core symbols
           | against redefinition and a way to do it anyway (possibly
           | shooting yourself into the foot).
        
           | JadeNB wrote:
           | Every language has the errors that it thinks you should not
           | be allowed to make, and the errors that it trusts you to make
           | on the assumption that you can determine whether you meant to
           | make them better than the compiler. People complain on both
           | sides of the permissiveness spectrum--they want a language
           | that allows them to make exactly the errors that they want to
           | make, but, of course, that set of errors differs from person
           | to person!
        
           | [deleted]
        
       | reifyx wrote:
       | Having played around with Clojure and Scheme for a while (but
       | never got too serious), I always thought homoiconicity and macros
       | were extremely cool concepts, but I never actually ran into a
       | need for them in my everyday work.
       | 
       | >Now, if we agree that the ability to manipulate source code is
       | important to us, what kind of languages are most conducive for
       | supporting it?
       | 
       | This is useful for compiler programmers, or maybe also those
       | writing source code analyzers/optimizers, but is that it? On
       | occasion I have had to write DSLs for the user input, but in
       | these cases the (non-programmer) users didn't want to write Lisp
       | so I used something like Haskell's parsec to parse the data.
       | 
       | The remote code example given in the post is compelling, but
       | again seems a bit niche. I don't doubt that it's sometimes useful
       | but is it reason enough to choose the language? Are there
       | examples of real-life (non-compilers-related) Lisp programs that
       | show the power of homoiconicity?
       | 
       | Same goes with the concept of "being a guest" in the programming
       | language. I have never wanted to change "constant" to "c".
       | Probably I'm not imaginative enough, but this has never really
       | been an issue for me. Perhaps it secretly has though, and some of
       | my problems have been "being a guest" in disguise.
        
         | momentoftop wrote:
         | > This is useful for compiler programmers, or maybe also those
         | writing source code analyzers/optimizers, but is that it? On
         | occasion I have had to write DSLs for the user input, but in
         | these cases the (non-programmer) users didn't want to write
         | Lisp so I used something like Haskell's parsec to parse the
         | data.
         | 
         | If you're talking about Haskell, you should be talking about
         | folk who write template Haskell, which is the macro system for
         | GHC. There are plenty of Haskell programmers who know how to
         | write Template Haskell, and there are plenty of Haskell
         | programmers who don't. By contrast, I don't think there's a
         | single Lisp programmer who can't write Lisp macros.
         | 
         | That's homoiconicity. Once you learn Lisp, you automatically
         | know how to write Lisp macros. Once you learn Haskell (or Ocaml
         | or Rust), you _don 't_ automatically know how to write macros
         | in that language (and the macro system may not even be portable
         | across compilers).
        
         | zmgsabst wrote:
         | I remember (faintly, from 20 years ago) using Lisp in game
         | programming to create rules for mobs, from within the game.
        
       | eatonphil wrote:
       | See also, discussion from last year:
       | 
       | An Intuition for Lisp Syntax (stopa.io) 130 points by codetrotter
       | on May 27, 2021 | hide | past | favorite | 114 comments
       | 
       | https://news.ycombinator.com/item?id=27307388
        
         | Jtsummers wrote:
         | And the year before:
         | 
         | https://news.ycombinator.com/item?id=24892297 - Oct 26, 2020
         | (198 comments)
        
       | cjohansson wrote:
       | Lisp is inspiring at first glance, but when you need to solve
       | complex problems like references, pointers, macros, byte-
       | compilation and native compilation it just is not expressive
       | enough like C, C++, or Rust. Neither is Javascript. Lisp could
       | not replace all other languages
        
       | retrac wrote:
       | Most well-written Lisp code has its structure indicated primarily
       | by the indentation. Some form of offset rule is the most commonly
       | suggested alternative Lisp syntax. It does make the tree
       | structure more obvious:                   define
       | (define (pos>? p1 p2)           pos>? p1 p2
       | (or (fx> (pos-row p1) (pos-row p2))           or
       | (and (fx= (pos-row p1) (pos-row p2))                fx>
       | (fx> (pos-col p1) (pos-col p2)))))               pos-row p1
       | pos-row p2             and                fx=
       | pos-row p1                 pos-row p2               fx>
       | pos-col p1                  pos-col p2
       | 
       | The left looks a lot like a typical AST for many languages, such
       | as C, shortly after parsing. As the article points out, it's the
       | easy access to this tree-list structure at runtime, that can be
       | easily handled as data by the program itself, that gets Lisp
       | programmers so excited.
        
         | amelius wrote:
         | To be consistent, I think the first part should be indented
         | like this:                   define
         | pos>? p1                  p2
         | 
         | (p1 and p2 on separate lines)
        
         | Jensson wrote:
         | > )))))
         | 
         | You could stack them like this in every language but only in
         | Lisp do people actually do it. Lisps bad habbit of stacking all
         | the parentheses like that is what makes it so hard to read. It
         | is easier to write that way, but it is very hard to understand
         | how many contexts up you just moved.
        
           | lispm wrote:
           | It's not a bad habit. It's just more practical.
           | 
           | Lisp lists are a data structure. One for example types
           | code&data and computes with an interactive Read Eval Print
           | Loop.
           | 
           | That's what one would write:                 CL-USER 35 > (+
           | (expt 23 2.4) (sin (* 44 0.23 22)))       1854.5603
           | 
           | No one would type:                 CL-USER 36 > (+ (expt 23
           | 2.4) (sin (* 44 0.23 22
           | )                                     )                    )
           | 
           | Also when Lisp deals with s-expressions, the more compact
           | notation is more useful: here Lisp does the layout itself:
           | CL-USER 46 > (let ((*PRINT-RIGHT-MARGIN* 30))
           | (pprint '(+ (EXPT 23 2.4) (SIN (* 44 0.23 22)) (COS (+ 12
           | 0.43 19.0)) (TAN (/ 1.4 0.77 3/4)))))            (+ (EXPT 23
           | 2.4)          (SIN (* 44 0.23 22))          (COS (+ 12 0.43
           | 19.0))          (TAN (/ 1.4 0.77 3/4)))
           | 
           | It's not                 (+ (EXPT 23 2.4          )
           | (SIN (* 44 0.23 22               )          )          (COS
           | (+ 12 0.43 19.0               )          )          (TAN (/
           | 1.4 0.77 3/4               )          )       )
           | 
           | Above is much harder to read and wastes a huge amount of
           | space on the screen. Imagine that lists of are much longer
           | and deeper nested. Finding the corresponding parentheses is
           | usually done by using the editor (select a whole expression,
           | have blinking or colored parentheses, etc.).
           | 
           | The main difference between Lisp and most other programming
           | languages is that programs are written as a data structure:
           | nested lists. Not only that: they are not static lists, but
           | we can compute with them - that's the main appeal. It's a
           | programming language, where it's easy and common to
           | manipulate lists, even programs as lists. Thus the notation
           | has not only be useful for reading code, but also for
           | input/output by humans and programs. There a compact notation
           | is much more useful, since tools will often create huge
           | amounts of nested lists. For example imagine a macro for some
           | Lisp functionality, like a complex loop expression. The macro
           | expanded code can often be ten times larger as the input
           | expression, yet we may want to see it in a debugger -> write
           | that expression in a compact way.
           | 
           | I let Lisp indent my code and the system-wide standard
           | indentation makes it easier to spot parentheses errors, since
           | all code has the same shape rules.                 (progn
           | (case foo           (var (eval foo)))         (fn (apply foo
           | args)))
           | 
           | Since all code gets indented during typing, I can easily see
           | that there is one parenthesis too much in the first case
           | clause...
           | 
           | I wouldn't care to see the parentheses aligned and it makes
           | the problem often more difficult to spot:
           | (progn         (case foo           (var (eval foo
           | )           )         )         (fn (apply foo args
           | )         )       )
           | 
           | I want the expressions to use less vertical space, so that I
           | can see that the opening parentheses of the CASE clauses
           | align. Anything else is just visual clutter.
        
             | Jensson wrote:
             | The standard way is to close inline things inline and close
             | multi-line things on a separate line.
             | 
             | Like this:                 (+ (EXPT 23 2.4)          (SIN
             | (* 44 0.23 22))          (COS (+ 12 0.43 19.0))
             | (TAN (/ 1.4 0.77 3/4))       )
             | 
             | Instead of                 (+ (EXPT 23 2.4)          (SIN
             | (* 44 0.23 22))          (COS (+ 12 0.43 19.0))
             | (TAN (/ 1.4 0.77 3/4)))
        
               | lispm wrote:
               | Lisp has lots of multi-line expressions.
               | 
               | That the expression is ended I can see in the typical
               | Lisp expression because of the block structure. The
               | single parentheses does make the layout very cluttered
               | and visually ugly. It gets even worse in typically larger
               | Lisp code or data.
        
               | db48x wrote:
               | It is very common for programmers who are new to Lisp to
               | close their parentheses in the former style. However, it
               | is a crutch that they soon do away with if they stick to
               | the language for any length of time.
        
               | lispm wrote:
               | There are two reasons why sometimes the parentheses are
               | on a separate line:                 ((paris (french))
               | (los-angeles (english spanish))        )
               | 
               | Something like above would be used if one often adds data
               | clauses to that expression.
               | 
               | Also when there are comments on the last line, then
               | sometimes the expression might be closed on the following
               | like:                 ((paris (french))                 ;
               | city 1        (los-angeles (english spanish))  ; city 2
               | )
        
           | User23 wrote:
           | Sounds like a good opportunity to oil up Chesterton's fence.
           | You may want to consider the possibility that the conventions
           | Lispers have been using for considerably longer than C or
           | whatever language you like to bikeshed style guides in has
           | existed were actually settled on for pragmatic reasons.
           | 
           | You're not alone though. Many nascent Lispers go through the
           | use reader macros to make Lisp look more like what they're
           | used to phase.
        
             | Jtsummers wrote:
             | Not just nascent Lispers. I inherited something like this
             | in a C codebase before (several times, actually, only one
             | that I had to actually edit though):
             | #define BEGIN {       #define END }       #define INTEGER
             | int       ...
             | 
             | Yes, they even did it with the types. Made for a weird
             | Pasctran language that the dev was apparently more
             | comfortable with. I think they actually got a C translation
             | of another program almost "for free" doing this. The real
             | thing was they didn't want to write new code and didn't
             | want to learn C, so they subjected everyone after them to
             | this horror.
             | 
             | Moral of the story: If you can't be bothered to learn a
             | language and its conventions, be honest and get another
             | job.
        
               | retrac wrote:
               | Pasctran is an ancient cult. [1] Some say they're
               | extinct. Others say its practitioners have just gone
               | underground, now that society will no longer tolerate
               | such things done in public. It's everywhere once you
               | start looking though, insidiously contaminating our
               | precious function bodies.
               | 
               | From the source to the original Bourne shell:
               | BEGIN            REG BOOL slash; slash=0;
               | WHILE !fngchar(*cs)            DO    IF *cs++==0
               | THEN    IF rflg ANDF slash THEN break; ELSE return(0) FI
               | ELIF *cs=='/'             THEN    slash++;             FI
               | OD         END
               | 
               | [1] https://research.swtch.com/shmacro
        
           | lvass wrote:
           | Not with proper indenting, or rainbow-delimiters/show-paren-
           | mode.
        
             | Jensson wrote:
             | I don't buy that, when you do the same in other languages
             | you get:                   for (var i = 1; i < 101; i++) {
             | if (i % 15 == 0) {               console.log("FizzBuzz");}
             | else if (i % 3 == 0) {               console.log("Fizz");}
             | else if (i % 5 == 0) {               console.log("Buzz");}
             | else {               console.log(i);}}
             | 
             | Why do you think nobody other than lispers writes code like
             | this? Is it really necessary to write code that way? If it
             | is better, why not does nobody else do it? They can also
             | use colored bracers and tooling, while lispers has written
             | code that way forever.
             | 
             | I'm sure a big reason people call lisp a "write only
             | language" is because of this strange convention of stacking
             | all parentheses in a big clump instead of formatting like
             | normal.
        
               | lispm wrote:
               | Yet, for Python the layout is like above, with
               | indentation being significant:                 for i in
               | range(1, 101):           if i % 15 == 0:
               | print("FizzBuzz")           elif i % 3 == 0:
               | print("Fizz")           elif i % 5 == 0:
               | print("Buzz")           else:               print(i)
               | 
               | It does not need the {} pairs then. Now, are we lost
               | because the {} pairs are missing?
               | 
               | In Lisp you need to learn to apply the same idea of
               | indentation being significant:                 (loop for
               | i from 1 upto 100 do          (cond ((zerop (mod i 15))
               | (write-line "FizzBuzz"))                ((zerop (mod i
               | 3))                 (write-line "Fizz"))
               | ((zerop (mod i 5))                 (write-line "Buzz"))
               | (t                 (write-line (princ-to-string  i)))))
               | 
               | Just imagine the grouping parentheses are not there.
               | 
               | The disadvantages of Lisps are basically two:
               | 
               | a) there are more parentheses because of the nested lists
               | being used to write code
               | 
               | b) one now needs to understand when (sin a) is actually
               | code and when it is data.
               | 
               | The advantage of Lisp syntax:
               | 
               | a) code is already a simple nested data structure
               | 
               | b) the indentation&layout of code can be (and often is)
               | computed from the data, while usually in Python the lines
               | and indentation are significant
        
               | aGHz wrote:
               | Do you want Python? Because this is how you get Python.
               | 
               | Joke aside, this is why I never understood this problem.
               | With proper indentation it looks essentially like Python
               | with a generous helping of your-father's-parentheses.
        
               | wtetzner wrote:
               | Who calls Lisp a "write only language" other than people
               | who don't know Lisp?
        
               | lispm wrote:
               | There is the classic Minsky quote:
               | 
               | "Anyone could learn Lisp in one day, except that if they
               | already knew Fortran, it would take three days."
        
               | na85 wrote:
               | I used to feel the same way but lisp gets easier to read
               | with practice, and unless you're writing code in
               | notepad.exe or nano or something, your editor will show
               | you the matching paren.
        
               | oedo wrote:
               | >when you do the same in other languages you get...
               | 
               | Your result should be unsurprising. Lisps have a minimal
               | syntax that naturally entails high levels of nesting:
               | (s-)expressions being used to represent functions,
               | control structures, data, etc.
               | 
               | Why should a particular style convention appropriate for
               | that kind of language necessarily transplant well to
               | JavaScript-- a brace-delimited, Algol-inspired language
               | with a lot of syntax?
        
               | lvass wrote:
               | IMO, because C syntax sucks and makes people actually
               | take time to read and interpret the brackets. In erlang
               | and I think others people don't newline between closing
               | delimiters either. In lisp the brain processes the parens
               | automatically.
        
           | daptaq wrote:
           | But why would you care about those trailing parentheses?
        
             | Jensson wrote:
             | Readability. By properly indenting the parentheses and
             | putting them on lines you will at a glance see which
             | contexts you just closed, that is how people format code in
             | every mainstream language.
        
               | CraigJPerry wrote:
               | With lisp is it not more common to use paredit or
               | similar? I.e. you don't edit code like in other
               | languages.
               | 
               | As you barf and slurp, you're interested in the "shape"
               | of code but you ignore the parens.
        
               | lispm wrote:
               | None of these 'mainstream' languages use a data structure
               | for writing and manipulating code like Lisp. The use of
               | Lisp is thus very different and Lisp programmers find a
               | more compact notation more useful.
               | 
               | > you will at a glance see which contexts you just closed
               | 
               | the text editor does that for me
               | 
               | > how people format code in every mainstream language
               | 
               | Python code formatting looks different from Java code
               | formatting.
               | 
               | Python code:                 for h in range(0, height):
               | for w in range(0, width):               v =
               | bw_image.getpixel((w, h))                      if v >=
               | avg:                   bm_image.putpixel((w, h), white)
               | else:                   bm_image.putpixel((w, h), black)
               | 
               | Lisp code just looks like that. Only with added
               | parentheses (because Lisp expressions are written as
               | nested lists) and prefix notation:
               | (dotimes (h height)         (dotimes (w width)
               | (setf v (get-pixel bw-image w h))                (if (>=
               | v avg)               (put-pixel bm-image w h while)
               | (put-pixel bm-image w h black))))
        
               | Jensson wrote:
               | Python also formats their parentheses the non lisp way.
               | You typically format things like this:
               | data = [         [1, 2, 3],         [4, 5, 6],
               | [7, 8, 9, [10, 11, 12]]       ]
               | 
               | Any language that uses closing symbols formats them that
               | way, in Pyhton in Java, in C etc. Lisp is the only
               | example where they don't properly indent closing symbols
               | and instead just put them all together at the last
               | statement.
        
               | lispm wrote:
               | Yet most Python code is written in the compact form I've
               | showed you, where code indentation is significant. Lisp
               | has the same model: the code indentation is significant.
               | But Lisp has the structure encoded in nested lists. These
               | nested lists are automatically formatted in the same
               | space saving way as Python code. Due to the significant
               | indentation, Python usually can avoid to have grouping
               | characters/symbols.
               | 
               | I see also lots of Python code where data isn't written
               | like you claim...
               | 
               | https://www.programiz.com/python-programming/matrix
        
           | zozbot234 wrote:
           | There is a real drawback to stacking more than three or four
           | closing parens like this, without spacing them out: it's hard
           | and in fact impossible to count them at a glance. Not an
           | issue if you have a code editor with auto paren matching, but
           | it can be an annoyance when reading LISP-like code on the web
           | or elsewhere.
        
             | actually_a_dog wrote:
             | > it can be an annoyance when reading LISP-like code
             | 
             | Just mentally collapse any number of consecutive close
             | parens into a thing that reads like "END."
        
               | lispm wrote:
               | In Interlisp one would write a super parenthesis, which
               | closes all open parentheses, upto an opening [ or the top
               | ( :                 (a (b c (d ]
               | 
               | and also                 (z (a [b c (d]             [e f
               | (g]]
        
           | wtetzner wrote:
           | > Lisps bad habbit of stacking all the parentheses like that
           | is what makes it so hard to read.
           | 
           | How much Lisp have you written/read? Closing parens on
           | separate lines would be a nightmare to read in any real-world
           | code.
           | 
           | Also, what makes Lisp hard to read is lack of familiarity.
           | It's not like C is easy to read for someone who's only ever
           | written Lisp. Lispers don't find Lisp hard to read.
        
         | actually_a_dog wrote:
         | > The left looks a lot like a typical AST for many
         | languages....
         | 
         | Right, and this is what's meant by the oft-repeated statement
         | "LISP has no syntax." Of course it has syntax in the sense that
         | if you were to go in and delete one of those parens, the whole
         | thing wouldn't compile, but the syntax it does have is 100%
         | regular.
        
         | bryanlarsen wrote:
         | And if anybody is interested in a well developed alternative
         | Lisp syntax in this manner:
         | 
         | https://readable.sourceforge.io/
        
           | retrac wrote:
           | Unfortunately, as with all standards, there's so many
           | wonderful alternatives to choose from. I rather like Wisp:
           | https://www.draketo.de/software/wisp
           | 
           | I wish all languages had the : operator that Wisp has. It's $
           | in Haskell. It simply says that what follows to the end of
           | line is wrapped in brackets. So:                 putStrLn $
           | somefunc $ process val = putStrLn (somefunc (process val))
           | 
           | Like a UNIX pipe, but moving to the left.
        
             | amelius wrote:
             | Instead of $ they could have used C/, since it looks more
             | like an opening bracket.
        
       | 100001_100011 wrote:
       | "There lies our problem: we can't use eval"
       | 
       | And there lies the solution: fix eval.
        
         | qsort wrote:
         | Yeah, just casually solve the halting problem while you're at
         | it, why not.
        
           | topaz0 wrote:
           | Fixing eval is almost exactly what the OP does. It's not that
           | hard. Everyone who has written a toy lisp can do it.
        
       | praptak wrote:
       | My intuition for Lisp syntax is "The opening parenthesis moves
       | one position to the left. Drop commas, keep whitespace.". So:
       | f (x, y, z)
       | 
       | becomes                   (f  x  y  z)
        
         | bkirkbri wrote:
         | Same here. I never understood why a paren on the left of a
         | function name "looks like fingernail clippings" but to the
         | right it's "just how code works."
        
           | taeric wrote:
           | Another plus one here. The commas baffle me, as more
           | languages decided to add optional trailing ones...
        
           | qsort wrote:
           | Larry Wall's remark was less about prefix notation and more
           | about how there's very little visual difference. I don't
           | necessarily agree with the criticism because that's the price
           | you pay for homoiconicity, and it's a price well worth
           | paying; but it was a more serious complaint than "lmao
           | parentheses".
           | 
           | See e.g. Clojure, that introduced #{} for sets, [] for
           | vectors etc.
        
           | kazinator wrote:
           | We have to try to understand the non-strawman position of
           | those who are genuinely turned off by Lisp syntax. The
           | problem for them isn't the position of the parenthesis or the
           | lack of commas: those things are probably fine for almost
           | everyone.
           | 
           | In Lisps, this notation represents all structures in the
           | program: definitions of functions, types and variables,
           | control statements and so on.
           | 
           | For the users who have some kind of problem with that, it
           | wouldn't be any better with the op(arg, ...) notation; and I
           | suspect that most would agree that it's even worse.
           | 
           | (For that matter, most programmers don't actually have
           | experience nesting the f(x, y, ...) notation to the same
           | depth that is seen in Lisp programs. Anyone comparing a
           | simple one-or-two-liner f(x, y, ...) with a modicum of
           | nesting to Lisp code that runs for pages and pages is doing
           | apples and oranges.)
        
             | taeric wrote:
             | Sadly, I'm not convinced that there is much more than the
             | straw man, honestly. Folks are predisposed to think it is a
             | hard to read language. And this is on large because
             | everyone says so.
             | 
             | Similarly, python. Is only a readable language because the
             | community insists it is.
        
       ___________________________________________________________________
       (page generated 2022-08-28 23:00 UTC)