[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)