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