An Intuition for Lisp Syntax

(stopa.io)

551 points | by simonpure 1271 days ago

28 comments

  • jFriedensreich 1271 days ago
    This is maybe the best introductions to Lisp i have seen, especially for a js dev like me it could hardly get more approachable and convincing.

    I could not help but thinking at the end, this is really awesome but s-expressions are kind of hard to read and reason about for my brain, it would be cool if we could generate them from more readable syntax, maybe something like javascript :D . Unfortunately the standard js AST seems to be not a great fit but i found a S-Expression encoder for js AST https://github.com/anko/eslisp

    This way instead of having to write s-expressions manually we took a whole circle and are back at js but with macros, not relying on eval and having a code is data representation...

    • wavegeek 1271 days ago
      > s-expressions are kind of hard to read and reason about for my brain

      I think it's likely this is just a matter of familiarity.

      Like the way that people think the Windows (/ whatever) GUI is intuitive - but when you test this by putting someone who has never used it in front of a screen, it actually isn't. "What do you mean if I write something and don't also 'save' it, whatever that is, it will disappear? This is supposed to be better than paper...".

      The text editor handles indenting and matching. After a while it is completely transparent.

      • jmiskovic 1271 days ago
        Maybe the last sentence needs a bit expanding, I think most readers already use a powerful text editor and don't see how standard features would help with lisp. The thing is, you don't really edit Lisp as text. Two commonly used extensions (for emacs, vim, vscode, sublime...) are paredit and rainbow. Those extensions kick in when lisp code is detected.

        It then feels more like tree manipulation than like writing code. Braces are always matched, you literally cannot delete the closing brace using normal text editing. Each tree depth as its own brace color so it's easy to distinguish between them.

        Restructuring tree to join two blocks of code or to extract block to higher level is exposed as command mapped to keyboard shortcut. It's not semantic editing in sense that extension is aware what is function and what is data, you can still make nonsense code. Still, reading and manipulating code is quite effortless.

        • githubalphapapa 1271 days ago
          > Two commonly used extensions (for emacs, vim, vscode, sublime...) are paredit and rainbow.

          In Emacs, you may find Lispy and Prism even better.

      • webreac 1271 days ago
        I have learned scheme before camllight (the ancestor of ocaml). The syntax was a pleasure compared to scheme.
      • username90 1271 days ago
        That becomes a no true Scotsman fallacy though. People argue "Either you like lisp syntax or you haven't used it enough to have a valid opinion". That is a bullshit argument.
        • ACow_Adonis 1271 days ago
          It is and it isn't :)

          Obviously opinions on the language shouldn't be given too much weight if a person hasn't spent too much time investigating/ using it.

          As a non-JavaScript person, all the JavaScript examples looks like gobbledygook unless I sit down and consciously think about what the examples are showing, and it kinda hurts my head a bit, but I can slowly force myself through it.

          But as someone with a few years of lisp under my belt, even the closure examples (and I've never used clojure) parse pretty close the speed of thought.

          It really is hard to explain to someone who hasn't used lisp for 6-12 months, but there really is a lisp "enlightenment" experience, which is totally unlike anything I've experienced with any of my other languages. I'm not saying this makes it better or worse than other languages, but I am saying it's real, and that lisp code is not only parsable, but exceptionally so to an experienced lisp programmer. One day you're sitting there thinking there's a whole lot of brackets, balancing and indentation in this godforsaken language, and then something just clicks over in your brain and you don't even see the brackets any more and it's all just data structure, and you see the shape of it, and you say to yourself "well that was pretty fucking cool" and from that day on you don't really see the brackets any more, it's all just names, indentation and data structures.

          edit: and it should also be recognised that an editor implementing parentheses balancing, indentation options, and keyword lookup/ completion also becomes a relatively trivial exercise when your code consists of variable names and structured data.

          • TeMPOraL 1271 days ago
            > and then something just clicks over in your brain and you don't even see the brackets any more

            In my experience, the Lisp enlightenment comes in stages, two big ones are 1) s-expression parsing you describe here, and 2) grokking "code is data".

            WRT. s-expressions enlightenment, I vividly remember when it happened to me. Few months into learning Lisp, I bought a hardcover SICP and, for some reason, decided to do exercises in it on paper. Few exercises in, as I was writing Lisp code with a pen in hand, suddenly something clicked in my head - like my brain JIT-compiled a new processing module - and from then on, I no longer needed to mentally count or track the parens. I just knew how many and where they were, it dropped to semiconscious level. Ever since, I'm very comfortable with s-expressions; in fact, I prefer them as a notation to Algol/C-like code.

            • jakear 1271 days ago
              I’ve literally seen first-hand GJS lose track of paren matching while lecturing on Sceme/SICP. It happens very rarely sure, but certainly not never. Probably about as often as experts in any other language/profession make silly mistakes. Perhaps you’re a better schemster than him, but I have my doubts :)

              I think no matter the language the syntax will disappear over time and your brain will learn to look at the character matrix and directly see logical constructs, moreover this will always apply cross-language to other languages with similar syntax. Fit example, I primarily work in TS, I have decent amount of experience in other C-style, and to me Rust is easily readable with very little experience actually using it.

              • capableweb 1271 days ago
                I'm not sure who GJS is but if you see any lisper editing text instead of operating on structures (with auto-balancing parenthesis and so on), it's 99% certain it's in an environment they are not familiar with, so they will make mistakes.

                I don't think anyone who write Lisp-like languages professionally doesn't use tools like parinfer/paraedit, where balancing parenthesis is not something you have to do.

                • jakear 1271 days ago
                  This was on a blackboard. And GJS advised Guy Steele in creating Scheme and coauthored SICP with him.
                  • capableweb 1271 days ago
                    Yup, makes sense that you make mistakes then, as these folks are surely used to not having to think about balancing parenthesis anymore. Just as over-reliance on GPS will make your navigation skills without a GPS worse over time.
                  • kgwgk 1270 days ago
                    > coauthored SICP with him

                    Not with him.

                • kgwgk 1271 days ago
                  I'm pretty sure there are people who write common lisp professionally whithout those tools. Are they even available in the LispWorks or ACL editors?
                • bear8642 1270 days ago
                  >I'm not sure who GJS is…

                  Gerald Jay Sussman

          • novok 1270 days ago
            I think it's like a language family thing. If your familiar with a C-language (or more accurately, AGOL style) then looking at other C-languages isn't as alien to you. While looking at a LISP family language, it's a different language branch and so you have to learn some new ways of thinking, which can be disorienting.

            Ex french & english vs french & indonesian. English and french can recognize words from each other and kind of see some similar grammar rules (the medicine vs. la médecine) while with indonesian those moments are not as frequent.

          • abhinav22 1270 days ago
            Hey :)

            You wrote a really nice comment ages ago, which partly inspired me to learn lisp and also it’s formatting.

            I still want clean it up a fair bit, but here’s the draft article:

            https://ashokkhanna-530.medium.com/formatting-lisp-5e28020b8...

            Hope it’s okay to use your quote! And if not, I’ll take it down

            • reegnz 1270 days ago
              I'd just use parinfer/paredit and forget about the whole formatting 90% of the time.
            • ACow_Adonis 1270 days ago
              perfectly ok from me, honestly I'm flattered :)
    • TeMPOraL 1271 days ago
      > This is maybe the best introductions to Lisp i have seen, especially for a js dev like me it could hardly get more approachable and convincing.

      The classical one is "The Nature of Lisp"[0], which introduces data-as-code through XML and Java build tools. Same idea, just with examples more relevant at the time of writing. Still worth a read for non-webdev programmers.

      Having learned Lisp, it's half funny, half disheartening to watch the industry repeatedly tries to rediscover "code as data", as people's configuration or data files in markup language du jour grow in complexity and eventually start directly encoding executable code... and then stop shy of embracing the code/data duality.

      --

      [0] - https://www.defmacro.org/ramblings/lisp.html

      • Perseids 1271 days ago
        I think that should be the marketing of Lisp: A necessary evil ;)

        But seriously speaking, for me Lisp would be much more appealing if it had been introduced to me as a specialized niche language, though for an important niche nonetheless, instead of as the be-all-and-all language for your superpowered startup [1].

        (For those that don't already have a goto list of counterarguments to "Lisp all the things": My main contra-point to Lisp is that with all the meta programming powers you get, you write yourself into your own little corner where no-one but you and your friends live. You want advanced syntax highlighting, linting, automatic refactoring for your special features? Write it yourself! You want outsiders to participate (think: new employees)? Write all documentation yourself, too!)

        [1] http://www.paulgraham.com/avg.html

        • TeMPOraL 1271 days ago
          Having worked in a Common Lisp startup, I'll only agree with half of your counterarguments :).

          > You want advanced syntax highlighting, linting, automatic refactoring for your special features? Write it yourself!

          That's true to an extent. Simple things are simpler in Lisps, because the syntax is trivial. So highlighting and structured editing are easy. The rest, is near impossible, at least for a full-featured Lisp like Common Lisp. That's a consequence of being an extremely dynamic language, that ships a compiler and intertwines parsing, compiling and execution. The flip side of being able to run arbitrary computation at compile time is that, in general, you can't know what the program will do until you run it. The dominant free CL IDE, SLIME, does just that: it queries your running Lisp image for its current state, to provide you with formatting and autocomplete and other hints.

          That said, "near impossible" is a function of community size. Had CL anywhere near as much popularity as Java or Python does, I'm sure folks at IDEA would make automatic refactoring work for CL as well :).

          > You want outsiders to participate (think: new employees)? Write all documentation yourself, too!

          That I strongly object to. Code generation and compile-time execution aren't magic, or even particularly hard concept. They're just another flavor of code. You manage it the same way as regular code - you package it into modules with well-defined interfaces, and document them. I've worked with CL in a team settings, and I've worked on legacy CL code that's as old as I am; it's not harder than "regular" legacy code. And you always have to write your documentation yourself, there's no escape from that, no matter what language you use.

          • flavio81 1270 days ago
            >in general, you can't know what the program will do until you run it

            It needs to be added that in Common Lisp, once the program is running you can do everything with it while it's running: Inspect the stack frames, change variable values, rewrite/recomiple/update functions, update class definitions, update objects to said new class definitions, save the current program state to disk,etc.

            So it's programming that is geared to RUNNING the program and modifying it while its running.

        • capableweb 1271 days ago
          Seems you're arguing for using all the powers a programming language gives you, nothing in particular about Lisp there. As with many things, it depends mostly on the people writing the code and their process, rather than what programming language they are using.

          I never inherited a Clojure-codebase that has been written during long duration, but I have digged around in a fair amount of Clojure-codebases that are open source, both user focused and libraries. Same with JavaScript. And I can say, as someone with more JS experience than Clojure, that the Clojure projects tend to be a lot easier to understand than the JS ones, probably not because of the language, but because of the habits that the language "forces" you into.

          • Perseids 1271 days ago
            I'm mostly arguing against extensive use of meta programming, which is promoted as one of the main reasons to view data as code, as meta programming is inherently disadvantaged for automated tooling. Formulated as a trade-off: When I have to choose, I prefer richness of IDE (and other tooling) automation to program creation automation that I write myself (i.e. meta programming).

            This trade-off is not restricted to Lisp, but also applies to e.g. C++ template meta programming. Compilers have gotten betters with templates, but still debugging the more advanced usages of templates can become hellish. Codifying the features the meta programming supplies in the language itself or in well supported libraries means that error messages get better and many usage scenarios are documented on Stackoverflow.

            I don't doubt your experience regarding Clojure vs JavaScript code bases, but this probably has to do with other reasons than the meta programming the original blog post is about?

            • wtetzner 1271 days ago
              > I'm mostly arguing against extensive use of meta programming, which is promoted as one of the main reasons to view data as code, as meta programming is inherently disadvantaged for automated tooling.

              Which is weird, because in most Lisps meta programming happens at compile time. It should be possible for tooling to just show you the expansion of any given macro invocation. In fact, when I used Clojure a decade ago, there was an Emacs command that would expand the macro invocation under your cursor, so you could see what code was actually being generated.

              So I don't really think that's a fundamental limitation. I suspect it's more related to the fact that Lisps aren't especially popular, and don't get the attention from tooling that other languages do.

              > Codifying the features the meta programming supplies in the language itself or in well supported libraries

              But without the meta programming, those libraries might not actually be possible to write. You will either end up with a more dynamic interface (doing meta-stuff at runtime), or a clunkier and more verbose interface. I think being able to expand a macro invocation to see what it turns into is enough for all but the hairiest of macros.

              • flavio81 1270 days ago
                > It should be possible for tooling to just show you the expansion of any given macro invocation. In fact, when I used Clojure a decade ago, there was an Emacs command that would expand the macro invocation under your cursor, so you could see what code was actually being generated.

                Exactly. On common lisp, for example, it's just a keypress, and it has a "macro stepper" so it shows the first expansion possible, then the second expansion, and so on and so on... until you end up with compiler primitives!

        • memling 1270 days ago
          > You want outsiders to participate (think: new employees)? Write all documentation yourself, too!)

          Shouldn't you be doing this anyway? Not trying to be snarky: it's that every job I've worked at has a classic underdocumentation problem. Tribal knowledge dominates, and the practicalities of shipping product overrun the need for teaching employees, new and old, about the idiosyncrasies of the system. [edit] This seems hardly an issue with LISPs or DSLs in particular.

        • kd0amg 1269 days ago
          > My main contra-point to Lisp is that with all the meta programming powers you get, you write yourself into your own little corner where no-one but you and your friends live. You want advanced syntax highlighting, linting, automatic refactoring for your special features? Write it yourself!

          This is contrary to my experience (though I've mostly used Racket rather than Common Lisp). Tracking what's a function vs macro, where an identifier is introduced/used, etc., is all baked into the underlying language and not something I have to write myself for every language extension I make.

          > You want outsiders to participate (think: new employees)? Write all documentation yourself, too!

          Were you planning to not document your internal-use utility code?

      • maximilianroos 1271 days ago
        Greenspun's tenth rule [0]:

        "Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp."

        Also:

        > Hacker Robert Morris later declared a corollary, which clarifies the set of "sufficiently complicated" programs to which the rule applies "...including Common Lisp."

        [0] - https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule

        • flavio81 1270 days ago
          >Hacker Robert Morris later declared a corollary, which clarifies the set of "sufficiently complicated" programs to which the rule applies "...including Common Lisp."

          This is referring to CL implementations that depend heavily on C.

          Many CL implementations now are almost 100% Lisp code.

        • drran 1271 days ago
          JavaScript (which is based on Scheme dialect of Lisp) is formally specified, well-developed, and fast, so quote above is outdated.
          • TeMPOraL 1271 days ago
            JavaScript isn't based on Scheme, it's what would have been Scheme if deadlines and marketing needs didn't make Netscape use a hacky toy language instead.

            The place where JavaScript contains "an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp" is Babel.

      • konjin 1271 days ago
        If you take anything away from that: brackets are the most compact way to serialize a tree, for some reason they are called s-expressions. The parse tree for lisp is the syntax tree, you just rename each bracket node to whatever the first atom in the bracket happens to be.

        One thing that XML could have done that s-expressions can't is a way to serialize a dag if proper nesting of tags wasn't required. I'm playing around with a language based on that, the mental load of having no clear way to delineate blocks visually makes it hard to reason about, unfortunately.

    • simongray 1271 days ago
      > I could not help but thinking at the end, this is really awesome but s-expressions are kind of hard to read and reason about for my brain, it would be cool if we could generate them from more readable syntax, maybe something like javascript :D .

      So many people start out thinking like this when they learn Clojure or some other Lisp and then after like 2-4 weeks of reading Lisp code, any C-like code starts to look comparatively irregular and Lisp feels much easier to read.

      • jFriedensreich 1271 days ago
        I heard that before but i wonder how much is survivorship bias :D it reads just so awkward to me as a programming language, even though i 100% dig the beauty and everything that it allows.
        • simongray 1271 days ago
          It's just a different paradigm than what most developers are familiar with so there is a learning bump, same as functional programming if all you've done is object-oriented stuff. The awkwardness is only temporary. If you can already see the beauty of Lisp code, then you just need to build a little project in it to make the awkwardness fade away.

          I recommend Clojure/ClojureScript. It's quite practical for web development and also an excellent gateway into functional programming.

          • arethuza 1271 days ago
            I used Common Lisp as my main language for about 6 years (long time ago now) - yes for the first few days the syntax is a bit jarring but I've found that any new language is fundamentally different in approach to what you are used to has that effect (pretty much the same happened the first time I saw C, Lisp, PostScript etc.)
        • wavegeek 1271 days ago
          > i wonder how much is survivorship bias

          Here is one anecdata. When my daughter was 14 I taught her a Lisp and then a traditional programming language. She found the random syntax of the trad formatted language bizarre.

        • Viliam1234 1270 days ago
          I used Java for 10+ years, including now, and I worked briefly in Clojure for less than 1 year. I think the Clojure syntax is way more reasonable, although I appreciate the maturity of tools used with Java.

          Two things were important to make it "click" for me:

          1) I realized that "Lisp has many parentheses" actually has it backwards. A Lisp code has about as many parentheses as an equivalent Java code; there is almost a 1:1 relation. It just has less of... unnecessary things. Which results in more "parentheses per square meter", but from this perspective that is a good thing!

          2) Some people complain that dense code is more difficult to read. But you don't have to turn everything into one-liners! You can format it into just as many lines as the Java code would have, except the lines will be much shorter now, and therefore easier to read. In Java, you don't have much freedom with formatting, because a typical statement takes more that half of the screen width, so you are stuck with one long column. Lisp with shorter statements gives you more freedom. You can abuse it to write hard-to-read code. You can also use it to design beautiful code that is easy to read. Also, there is a huge difference in legibility of a code that fits in one screen (so you can see it whole at once) and one that does not; and the Lisp functions are shorter on average.

        • filoeleven 1270 days ago
          I can only speak about Clojure, since it’s the only lisp I know. After just a few days of playing with it though, I started to see the parentheses as a warm and friendly hug, wrapping everything inside of them in its own scope that doesn’t affect its parents. Reading JavaScript, my main driver, became harder in comparison.

            function doSomething(a, b) {/* something! */}
          
          is okay, since the “function” keyword is a simple indicator, and it’s clear that it’s a function declaration. The kids these days often use

            const doSomething = (a, b) => {/* whatever */}
          
          and that is awful for readability. It’s much harder to scan for function definitions amidst value declarations. I’m nearly 20 chars in before I even know it’s a function. Worse yet is something like

            const doSomething = (a, b) => b => “some closure”;
          
          This threatens to stretch my capability to understand the context I’m in when reading it. “b” is just kinda floating there amongst the infixed arrows, and I have to read on to know it’s an argument, and I have to run through the calculation of what “b” is every time. Compare:

            (defn do-something [a b] (fn [b] “some clojure”))
          
          I can count the parens, there is no implicit scoping of the function body. My editor can too, which means I often don’t have to. It’s obvious that it returns a function. This part is Clojure-specific, but I also have strong guarantees that the returned function cannot pull the rug out from under me with whatever I pass to it by arbitrarily mutating some argument.

          To me at least, (fn-name args) was both more readable and makes more sense than fnName(args) after a few days, and I never learned a lisp until I was 37. Maybe I just found a style preference later in life, marking me as a lisp survivor. Maybe we’ve just been doing it wrong for decades and have grown accustomed to it. I can’t say. But the power of the language itself coupled with the code editing features it enables makes me think that the whole field has been on the wrong track, or maybe even off the rails, for decades.

          The JS code I write now is more flexible, more resilient, more testable, and more maintainable as a result of learning Clojure. I doubt anyone can say the opposite: that a C-style language improved their understanding of lisp. (Not addressing you with this part, dear poster, since your gripe is rooted in the syntax.)

          [NaN] “kids these days“ is just friendly ribbing. I’ve got the grey beard now, so I‘m free to play the part. Language maintainers tend to be older, and we cause more problems.

      • username90 1271 days ago
        Everything is so extremely context sensitive and has much more focus on text over symbols than other languages, which isn't a problem for computers but is a huge problem for humans. Therefore lisp is objectively harder to read for humans. If you have never read other code and is super familiar with lisp it is easier to read, but anyone with experience with both will find other languages much much much easier to read thanks to the much better UX design.

        I don't see how you can argue otherwise. It is as if you have no clue at all about how UX works. You can't get used to everything, there is a hierarchy of syntaxes, and lisp syntax is just plain bad for humans.

        • wtetzner 1271 days ago
          I have experience with tons of languages, lisps and non-lisps.

          I don't really get this argument. It's not any harder to read `(if foo` than it is to read `if (foo)`. It's purely a matter of what you've gotten used. I don't think it's an objective measure, it's purely about what you've already trained your brain to pattern-match on.

          > and has much more focus on text over symbols than other languages

          Funny how you get the exact opposite complaint about languages like Perl.

        • filoeleven 1270 days ago
          Let’s talk about text over symbols and readability.

          Return the maximum value of the array “numbers”. The languages are listed in descending order of text > symbol.

          Clojure:

            (apply max numbers)
          
          JavaScript:

            Math.max(...numbers);
          
          APL (I couldn't find how to apply this to an arbitrary array):

             ⌈/ 4 3 2 7 5 1 3
          
          Here’s a guess:

            ⌈ numbers
          
          The most aymbol-focused is the least readable to me, and Clojure is the only one that returned good results from a Google search to figure out what was going on. Both “JavaScript ...” and “APL ⌈” gave me irrelevant results.

          Furthermore, how is this

            (if a
                true-cond
                false-cond)
          
          harder to read than this, aside from familiarity?

            if (a) {
              trueCond
            } else {
              falseCond
            }
          
          In the latter case, the if/else requires a special syntactical structure. And since it cannot be used as an expression, the common desire to conditionally assign a result spawned the ternary operator: a whole separate syntax that only applies when you want to assign a result to something. In lisps, you just use “if” wherever you need a binary conditional.
        • Perseids 1271 days ago
          While I agree with your statement, I would probably downvote your comment if it wasn't grey already. The reason is that you mostly state your opinion (aggressively so), but fall short on arguing why your opinion is true. If you can extend your argument / extend the list of arguments, I think your comment would find more appreciation.
        • username90 1271 days ago
          People saying that Lisp is as easy to read since you get used to it is like people saying that salad is as tasty as cake since you get used to it. Sure someone eating cake for the first time after having only eaten non sugary stuff for a long time will find it a bit jarring, but anyone eating both will find cake so much tastier, which is why people overeat cake so much.

          Yet many people will still adamantly say that they find salad tastier. And still go on and eat cake and get fat. They are just lying to themselves, I'm not sure why. Lispers are the same way, its just a reality distortion field.

          • minerjoe 1270 days ago
            Why say lispers are "reality destorted"? Maybe they have learned something you have yet to understand?

            Have you ever programmed a decently sized lisp program using an editor that allowed you to work at the AST level?

            Until then, there's just a large probability, regardless of how tasty cake is, that your displaying classic ignorance.

    • coldtea 1271 days ago
      >I could not help but thinking at the end, this is really awesome but s-expressions are kind of hard to read and reason about for my brain

      is

        (map foo '(1 2 3))
      
      that more difficult than:

        [1, 2, 3].map(foo)
      
      ?

      Or:

        (let ((array #(1 2 3 4 5)))
        (vector-set! array 0 3)
        (vector-ref array 0)) 
      
      that more difficult than:

        let array = [1, 2, 3, 4, 5];
        array[0]=3
        array[0]
      • eMSF 1271 days ago
        Is

          (* (f (+ 2 x)) 5)
        
        more difficult to parse than:

          5 * f(x+2)
        
        Considering that we're familiar with the latter syntax (more or less) since kindergarten, I'd say... yes. And the idea that you'd somehow start to consider something you've been doing all your life as “irregular” after 2–4 weeks of Lisp cure is just hilarious.
        • Viliam1234 1270 days ago
          Depending on how frequent are complex equations in your code, you could make a macro for it, and write it e.g. like this:

            (math 5 * f (x + 2))
          
          I might be tempted to do so if I frequently encountered math expressions of depth 3 or more, which in my short Lisp career I didn't.

          Also, I would probably rewrite the expression as:

            (* 5 (f (+ x 2)))
          
          to make it easier to read by putting the simple operand first; or in case of two difficult operands I would use formatting:

            (* (f (+ x 2))
               (g (+ x 3)))
          
          Note that a good editor would check that the parentheses match the formatting, so I wouldn't really count that 3 closing parentheses are needed in the last line; they would be inserted automatically.
        • coldtea 1271 days ago
          >And the idea that you'd somehow start to consider something you've been doing all your life as “irregular” after 2–4 weeks of Lisp cure is just hilarious.

          And yet we learn all kinds of syntax that we haven't been familiar at all (zero indexing, x=x+1, etc which we used to the exact inverse: math assignments being immutable or denoting an equation not increment, etc),

          -- not to mention advanced stuff like generics, futures, closures, etc --

          and we seem to manage just fine...

      • Joker_vD 1271 days ago
        Yes. It's all personal, of course, but I find that the typographical variety of "traditional" languages with syntax makes it way easier to me to read/parse them than LISP. Parens, parens, everywhere, nor a drop of structure.

        And when it comes to macros and (pseudo)-quoting, the LISP is hands down more obnoxious (again, for me) than, say, Python's f"{}".

        • wtetzner 1271 days ago
          > It's all personal, of course, but I find that the typographical variety of "traditional" languages with syntax makes it way easier to me to read/parse them than LISP.

          I wonder how much of that has to do with your familiarity with "traditional" language syntax. For example, when I started writing Lisp, I had a similar opinion. But I write Clojure professionally for a while, and that disappeared. Now I haven't written Lisp in probably 7 years, but I still have no problem reading it.

          > Parens, parens, everywhere, nor a drop of structure.

          On the other hand, in some sense it's all structure. There are certainly advantages to having everything be delimited.

          • Joker_vD 1271 days ago
            Yes, it's just that it's the useless (unless you write an AST-rewriting macro) structure that gets in my way. Say, naming a thing, a function definition, and a function invocation are all very visually different in, say, JS, while in LISP it's just a slightly different pattern of parens and two keywords ("let" and "lambda") that are, of course, not actually keywords but just happen to be interpreted in that way by the eval.

            And yes, human ability to pattern-match things is astonishing, I am sure if I were to program exclusively in Scheme for half a year, I too would one day grow accustomed and used to it. But do I want to? I am really not convinced about that. A human can get used to pretty much anything, even to almost constantly being in mild pain, but... no. I'd rather just not.

            • minhm 1270 days ago
              > it's the useless (unless you write an AST-rewriting macro) structure

              In a good JS editor, how many key combinations and mouse clicks are required to jumping into, jumping out of, and cutting a block of code (eg. a function definition or a conditional expression), or transposing, merging, splitting, annexing, and de-annexing 2 blocks of codes? It usually takes me at most 2 key combinations with a Lisp editor (including navigating the cursor to the right place), thanks to Lisp's uniform structure.

              > Say, naming a thing, a function definition, and a function invocation are all very visually different in, say, JS

              Aren't these also visually highlighted in a Lisp editor as well?

              Besides, a Lisp editor can optionally blur the parentheses so users don't mentally have to.

              (edit: formatting, recounting the key presses required)

              • kagevf 1270 days ago
                > It usually takes me at most 2 key combinations with a Lisp editor (including navigating the cursor to the right place)

                Is that in emacs/slime or another editor?

                • minhm 1268 days ago
                  Emacs/slime.
              • dunefox 1269 days ago
                > Besides, a Lisp editor can optionally blur the parentheses so users don't mentally have to.

                How do I do that with spacemacs?

                • minhm 1268 days ago
                  Try:

                  ===

                  (package-install 'paren-face)

                  (global-paren-face-mode 1)

                  (custom-set-faces '(parenthesis ((t (:foreground "gray50"))))))

                  ===

                  (edit: formatting)

                  • dunefox 1268 days ago
                    In the user init file? I'm not an expert on spacemacs.
              • Joker_vD 1270 days ago
                I believe it's also 1 or 2 shortcuts, thanks to the IDE's understanding of the language syntax, unless I misunderstood your scenario?

                > Besides, a Lisp editor can blur the parentheses so users don't mentally have to.

                Besides, another languages can throw away the parentheses entirely so neither users nor editors don't mentally or visually have to.

                • minhm 1270 days ago
                  > I believe it's also 1 or 2 shortcuts, thanks to the IDE's understanding of the language syntax, unless I misunderstood your scenario?

                  I have not seen transposing, merging, splitting, annexing (moving a block into the inside another block), and de-annexing (the opposite of annexing) 2 blocks of codes in JS without using the mouse yet, so I just want to check.

                  > Besides, another languages can throw away the parentheses entirely

                  JS uses parentheses for grouping complex arithmetics and for function's argument lists (eg. func(arg) in JS vs. (func arg) in lisp). JS also uses curly braces to mark code blocks, which is similar to Lisp parentheses but at the cost of more complex parsing for the compiler and the mental distinguishing between functions' argument lists and code blocks on the coder (they are just lists).

                  JS statements use semi-colons, which makes editing them feel like editing lines of codes while editing Lisp statements is editing nodes of a tree, which is a very different experience.

                • dunefox 1269 days ago
                  > Besides, another languages can throw away the parentheses entirely so neither users nor editors don't mentally or visually have to.

                  Which mainstream languages have no brackets?

          • capableweb 1271 days ago
            > Clojure professionally for a while, and that disappeared. Now I haven't written Lisp in probably 7 years, but I still have no problem reading it.

            Slightly off-topic but interesting none-the-less. After starting to program with Clojure both as an hobby and professionally, how do you go back something that is not lisp/repl driven?

            I've tried time and time again to go back to JavaScript, as I used to be OK with it, but I just cannot justify the hassles I have to fight with everyday, compared to if my co-workers just picked up Clojure instead.

            • wtetzner 1271 days ago
              Going back to something that's not REPL-driven takes some adjustment, but I try to find and focus on the advantages the other language has, instead of on what it's missing. For example, when using OCaml (which incidentally does have a REPL, but I don't use it often), I get a lot of value from the type system and the module system.

              Even Java has advantages in terms of tooling, and from Java 8 on, you can write code using some Clojurish idioms with streams. Though for immutable data, you need something like Lombok + pcollections.

              JavaScript, on the other hand, really doesn't offer anything over Clojure, so I can see why you'd struggle going back to it. It's just a downgrade.

            • lukashrb 1270 days ago
              You can get some of the interactivity through unit tests. Admittedly, the feedback loop is not always that tight as with the repl.
    • masukomi 1271 days ago
      There's a curly-brackets based infix formatting you could use if you'd prefer.

      https://srfi.schemers.org/srfi-105/

      Or, you could use T-Expressions if Python style whitespace indentations to represent code nesting is easier on your brain.

      https://srfi.schemers.org/srfi-110/srfi-110.html

      Scheme and Lisp are VERRRRY Flexible.

      • athenot 1270 days ago
        Thank you! I'm in the camp of "Curious about Lisp but never jumped into it" and was wondering if indentation could reduce some of the bracket-noise.

        This is VERY elegant!

    • chriswarbo 1271 days ago
      > it would be cool if we could generate them from more readable syntax

      There are a few options out there; maybe take a look at Sweet Expressions? Some Lisp implementations have native support for different syntax, but the nice thing about these formats (which are essentially just serialisations of concrete syntax trees) is that we can convert them into the expected format automatically, e.g. using a pre-processor.

      I have a go-to page on my blog for discussions involving s-expressions, including links to various alternative syntaxen: http://chriswarbo.net/blog/2017-08-29-s_expressions.html

    • miki123211 1271 days ago
      This is exactly what Elixir is doing!

      It has nice, readable, ruby-like syntax, but it's a Lisp at the core. Really powerful macros (see Ecto), and a syntax that isn't off-putting to the majority of programmers.

      Most things in Elixir, including constructions like if and def, operators and even module accesses (`Module.function`) are compiled to a simple AST based on function calls.

      Because that AST isn't usually written by humans, besides the function and the arguments, each node might contain additional context information, like the file and line number the call appeared in. That lets you do some really cool stuff, like distinguishing x (a variable you define) from x (defined by a macro), which is a frequent source of bugs in other languages that support macros.

      99% of Elixir is syntax sugar over function/macro calls, and its true AST is smaller than Clojures.

      You can read more here: https://hexdocs.pm/elixir/syntax-reference.html#the-elixir-a...

    • fao_ 1271 days ago
      > I could not help but thinking at the end, this is really awesome but s-expressions are kind of hard to read and reason about for my brain, it would be cool if we could generate them from more readable syntax, maybe something like javascript :D

      Little known fact: The people who came up with lisp wanted to do this, too.

      https://www.wikiwand.com/en/M_expressions

      • minerjoe 1270 days ago
        I think the story continues to where, in the beginning, they thought a change would be good, but after seeing the benefits that s-expressions gave, changed their minds.
    • pgt 1271 days ago
      Lisp has only one rule called the Operational Form:

         (operator arg1 arg2 arg3 ...)
      
      The Operational Form is just a list denoted by parentheses. The operation or 'function' comes first, followed by its arguments or operands, e.g.

         (+ 1 2 3)
         => 6
      
      The + symbol resolves to the plus function and is passed in the arguments 1, 2 and 3. Nested forms are evaluated inside-out:

         (+ 5 (- 10 6))
         => 9
      
      Traditionally, Lisp only has one data literal: `(linked lists)`, but Clojure adds `[square brackets for vectors]` and `{:curly brackets}` for hash maps. The colon denotes a keyword, which is a symbol which only ever resolves to itself and is commonly used for labeling things, e.g. keys in a map.

      Notice how the operational form is just a list with some symbols and data literals. In Lisp, the syntax for writing data structures and the syntax for writing code is the same syntax. And since a function operates on data and produces new data, what if a function could operate on code and produce new code, since code is just data?

      We call such a function a macro (and I don't mean Excel macros). Macros run at "compile-time" (technically 'read time') and the code output is executed at "run-time" (or during 'evaluation'). The benefit of macros is that if your language is missing a feature, you can add it.

      You can see this in practice by looking at the source code for the `and` and `or` logic functions in Clojure, which are typically built-ins, but in Clojure they are just macros bootstrapped on top of the special forms `if` and `let`: https://github.com/clojure/clojure/blob/38bafca9e76cd6625d8d...

      Clojure only has 13 special forms:

          [fn let let loop do while . if def recur
           try catch throw quote var
           monitor-enter monitor-exit]
      
      Everything else is built on top of that.

      When I started learning Clojure, I found the ClojureScript Koans to be very helpful in getting a feel for the semantics and to become familiar with the argument placement: http://clojurescriptkoans.com/

      If you come from a traditional OO-background, my condolensces and I recommend starting with Rich Hickey's 2-hour talk, "Clojure for Java Programmers": https://www.youtube.com/watch?v=P76Vbsk_3J0

      • kyberias 1270 days ago
        > If you come from a traditional OO-background, my condolensces

        Is this condescending attitude really necessary or useful?

    • agumonkey 1271 days ago
      now you can go read norvig lisp in python

      very similar, build out of python []

  • blackbear_ 1271 days ago
    Can anybody recommend resources for the "next level"? I get that "code is data", the "unless" example is nice, but what I don't get is: why would I want to do that?

    As a non-LISP developer I get by without macros and all the fancy things that are possible with LISP. I would like to see a strong case of daily tasks that are much easier with LISP macros. Anybody?

    • CraigJPerry 1271 days ago
      If you buy into the argument that less code, that writing code at a higher level of abstraction results in less time to market, less maintenance overhead, more responsiveness to change in future, less bugs etc. Etc. If you believe a 1000 line program is worse than a readable 100 line program that both do the same thing, then lisp with its macros is that thing.

      Super expressive and readable. I went from not being able to read clojure to now having written a few apps in it and feeling comfortable with the syntax in a few weeks.

      I definitely write less code to achieve the same ends but it’s still early days for my lisp adventure.

      • mythz 1271 days ago
        The argument isn't whether less, readable, higher-abstraction code is better than its opposite, it is. It's whether or not a real-world LISP code-base espouses these qualities.

        Which I don't believe it does, IMO the primary reason LISP isn't mainstream is because it results in less readable code for humans (i.e. the primary objective of programming languages), it's semantically the perfect minimalist language for a machine but I don't believe it's optimal readability for humans. Other disadvantages include lack of typing & poor tooling support. There's certainly domains it excels at due to its intrinsic qualities but I don't see it ever becoming a popular mainstream general purpose programming language.

        • monsieurbanana 1271 days ago
          First rule of macros is: do not write macros. At least in clojure, it's rare that I stumble upon macros in the wild. But when they're needed (someone else wrote about core.async in this thread) they are very useful.

          After one year of clojure, it's annoying for me to read classic C-like languages. There's also people who prefer reading code without syntax color, so clearly this is a subjective matter.

          I find clojure tooling really good, and it's hard to go back to a non-repl oriented workflow (this is not at all the same as something like python or javascript repl). But this is admittedly a gray area, there's areas where it's immature compared to other languages, and others where it shines because of it's repl-oriented nature.

          The lack of typing is certainly a sound argument, although my personal opinion is that for many use cases it isn't required. This is going to be a contentious opinion, I'm sure.

          Note that clojure has an advantage on this over other dynamic languages: idiomatic clojure code means that pretty much everything is pure data, in basic structures: maps and vectors. So the functions that you use to operate on your data are always the same. Over time you'll learn them, and they won't change between domains nearly as much as, say, if you were coding in OOP oriented python.

          • mythz 1271 days ago
            I've also spent a bit of time with LISPs, when learning Clojure I ported the Clojure 101 LINQ Examples [1] and did appreciate a lot of niceties that Clojure brings with it where the additional syntax actually added to its readability, e.g. the usage of vector syntax for function params provided a welcomed visual separator from its implementation body. Basically all its additional syntax improves readability over a LISP's typical clumped sea of parens where you can more quickly discern different constructs from a glance which would otherwise take me a lot more time & effort trying to determine the boundaries of each expression whilst evaluating them in my head, effectively conveying that the minimal s-expression syntax that's optimal for the compiler isn't optimal for humans.

            So when it came time to implementing a .NET LISP [2], I adopted much of Clojure's additional syntax for improved readability & interop with .NET APIs [3]. But you can only improve LISP's syntax so far, e.g. its Template libraries for HTML generation [4] make for horrible HTML DSL's which looks nothing like the HTML it's supposed to generate. The solution to overcome this was basically to not to use LISP for templates, instead create a multi-language scripting language [5] that embeds lisp into it allowing it to Combine strength's of all languages [6], e.g. use LISP for algorithms and Handlebars / JS Expressions for templating.

            The REPL is definitely one of its super powers which is one areas where it shines & basically the primary use-case where I still use it. I've created a live "watch" mode & deep integration with .NET libs that I use for discovery, e.g. run DB queries, call HTTP APIs, execute shell scripts, etc. [7]. It especially shines for being able to open a REPL session with a remote production .NET instance letting me inspect its live running state & invoke system functionality like querying its configured RDBMS, executing redis commands, send tweets, emails, etc [8], I've even got it to Live Script Unity objects in-game :) [9], which speaks to the power & elegance of LISP that's able to achieve so much with so little code.

            At the same time I don't think REPL-based programming is all that useful during normal development, you can execute encapsulated code fragments fine, but most of the time I'll need my whole environment constructed before being able to inspect it as I would when debugging, so I find it useful for opening a REPL session into a live running instance, but not using the REPL to construct the live instance. So for my dev workflow, static analysis & typing, great IDE, tooling + debugging is a lot more useful.

            [1] https://github.com/mythz/clojure-linq-examples

            [2] https://sharpscript.net/lisp/

            [3] https://sharpscript.net/lisp/#net-interop

            [4] https://www.cliki.net/HTML%20template

            [5] https://sharpscript.net/docs/syntax#language-block-modifiers

            [6] https://sharpscript.net/docs/syntax#combine-strengths-of-all...

            [7] https://sharpscript.net/lisp/#run-and-watch-lisp-scripts

            [8] https://sharpscript.net/lisp/#techstacks-tcp-lisp-repl-demo

            [9] https://sharpscript.net/lisp/unity

        • ccortes 1271 days ago
          >IMO the primary reason LISP isn't mainstream is because it results in less readable code for humans

          I disagree. Being a mathematician LISP just feels natural and the ugliest, most horrible and hard to read language by far is java (IMO, of course). But that's just probably because all my life I've been thinking about functions and not objects.

          If your background is OOP and look at some LISP there's no surprise if it looks unreadable, even more so if you have no real motivation to understand it.

        • capableweb 1271 days ago
          What is or isn't readable can be judged by two qualities. The first one is if it's being familiar to what you're used to and the second one how long it takes to learn if you're not familiar with it.

          Now I learned C-like language before I learned Lisp-like languages, so that might be the reason I felt I understood Lisp way faster than I felt I understand C-like languages. There is simply less to learn about the language and more to learn about conventions, while C-like languages always have bunch of extra syntax you have to learn, otherwise the compiler cries.

          > Other disadvantages include lack of typing & poor tooling support

          Not sure these are inherent to Lisps. There are plenty of typed lisps, and even the ones that don't have types, can have types added to them after the fact, just because they are Lisps.

          Regarding tooling, I'd say that the entire C-like ecosystem is far behind any Lisp language regarding tooling. I mainly see people using "println"'s for debugging, and sometimes using a "line debugger", while Lisp developers modify their code at runtime and have a far more advanced debugger, something C-like languages will probably never do as well as Lisp-like languages does it, again because of s-expressions and the code they make you write.

          • username90 1271 days ago
            > What is or isn't readable can be judged by two qualities. The first one is if it's being familiar to what you're used to and the second one how long it takes to learn if you're not familiar with it.

            So your argument is that all code is as easy to read as long as you have done the work to get familiar with it? I hope you understand that it is bullshit. Brainfuck isn't easier to understand than python no matter how much time you spend with brainfuck.

            • capableweb 1271 days ago
              No, that is not my argument. I'm sorry if I didn't explain myself well enough for everyone to understand.

              My point is that "readability" is composed by many factors, not just one. What is "readable" to some will not be "readable" by others. It depends.

              For someone who knows Fortran, learning a Fortran-like language is easy (like C or JS). For someone who knows Common Lisp, Clojure is easy. But for someone who knows Fortran, Clojure is less familiar, hence the code will, on a glance, look less "readable".

              The other factor is if something is "simple" by itself.

              Most of this view comes from Rich Hickey, who wrote Clojure. He discusses "readability" or "simplicity" rather, in his talk "Simple Made Easy". If you haven't seen it before, do yourself a favor and watch it: https://www.infoq.com/presentations/Simple-Made-Easy/

              He'll explain it much better, and with further points, much better than I ever can.

            • wtetzner 1271 days ago
              > Brainfuck isn't easier to understand than python no matter how much time you spend with brainfuck.

              I don't disagree with your overall point, but I actually wonder how true that is. If you spent years working in Brainfuck, and had never touched Python, I suspect it would take a bit of effort before Python was as readable to you as Brainfuck.

        • CraigJPerry 1271 days ago
          >> the primary reason LISP isn't mainstream is because it results in less readable code

          It’s fresh in my head just how unreadable i found Clojure when browsing source of random projects on github a couple of weeks ago. Its not that penetrable to begin with.

          However, that was only a couple of weeks ago, since then i’ve come to really appreciate the simple magic of lisp syntax.

          I’ve also been able to put it to practical use rather than just appreciating it - i’ve been spoiled by Calva in VS Code, structural editing that doesn’t just work, it actually flows - i’m no stranger to structural find and replace in Intellij but this is different, this smoothes an impedance mismatch between our thinking and expressing code.

    • auganov 1271 days ago
      Threading macros, core.async in Clojure that does quite a bit of code rewriting, anaphoric macros, with- macros that bind to some context, destructing in Clojure, slingshot's try+/catch+ in Clojure.

      None of that is strictly impossible in languages without macros but best case you'd have to wrap everything in a copious amount of lambdas making for very nasty looking code. Worst case some compile time optimizations wouldn't be possible.

      Any single macro isn't THE killer macro, but giving all of them up is a big deal. Similarly replicating semantics of a single macro in some other language may be doable but getting many of them to compose would be tough.

      Most popular languages tend to get new keywords, operators and other forms of syntax over time implicitly acknowledging limitations of the "user space" syntax. You don't see that as much in LISPs since most such things can be implemented as a macro.

    • dgb23 1271 days ago
      Macros are in practice a tool to reduce boilerplate. Which is why they are typically written for libraries and used in application code.

      For example parametrization and modularization are used to keep code DRY and separated at a functional level.

      However this can only get you as far as you still write ceremonial repetition around using those functions/modules/objects. Macros can get your code to that extra level of brevity and clarity.

      • chriswarbo 1271 days ago
        I've also used macros to execute some expensive data processing at compile-time. I had a fixed dataset containing named sets of strings, and I wanted a program which reads in a set of strings and returns the names of any supersets which appear in the dataset. For example, if the dataset contains 'x = {a, b, c}', 'y = {a, b, d}' and 'z = {a, c, d, e}' then a query of '{a, c}' should return '{x, z}'.

        I managed to speed this up a lot by using a (TemplateHaskell) macro to (a) read in the dataset from an external file at compile time, (b) transform it into a structure that has fast lookups and (c) serialise that structure to a Haskell syntax tree. Not only did this make lookups faster at runtime, it also eliminated all the error checking for locating/reading/parsing/etc. of the dataset: any problem would cause a compile error; if compilation succeeded, the resulting program didn't have to know or care about any of that.

    • vindarel 1271 days ago
      They are useful to refactor code, make it more terse, and thus ease one's life. For example, I am writing API endpoints. Each route uses the same scheme, they need to hold a list of messages with their status code which will be sent to the user. In Python, I must currently repeat their declaration in each function. Boilerplate. What should I do, use classes? Ugly solution. A quick `with-my-variables` macro would write the boilerplate for me, ensuring it is written at one place, making it easier for maintenance, future refactoring, etc.

      Python has decorators. They have a specialized, limited interface. Sometimes they are limiting you. A macro would impose no limit.

    • jimbokun 1271 days ago
      I think a good candidate for macros is anything you would do with Annotations in Java or similar languages. Macros are a much cleaner and easier to reason about way of doing code generation than whatever it is Java annotation processors do to generate code.

      And in the same way you should think twice about introducing a new code of annotation to your program, you should think twice about adding a macro to a Lisp program.

      But boy, when you really need it, it can really be a life saver.

    • threeqhan 1271 days ago
      They're great for implementing complex language semantics like cooperative multitasking (using macros to expand yield/resume points into a state machine for stackless coroutines, for example). Or cool object systems like type classes and multi methods.

      Smaller stuff like hand rolled parsers and serializers are much easier to get working fast (both time to write and time to run) using macros. Since a good chunk of my work is data flow in one form or another, I miss LISP macros a lot.

    • mnemonicsloth 1271 days ago
      If you want to learn a little bit about lisp, you might try some of Paul Graham's writing on the subject. http://www.paulgraham.com/lisp.html One thing you'll learn there is that it's possible to define a lisp interpreter, in lisp, in about a page of code.

      But if you want to really appreciate macros, you'll need to read some books. There's a lot to learn.

      My introduction to lisp was ANSI Common Lisp and On Lisp by Graham. On Lisp is all about macros. You need ACL to understand On Lisp. What you learn is pretty impressive -- I seem to recall one of the later chapters of On Lisp features a compiler for Prolog in two and a half pages of code -- but it requires a certain amount of supporting material. Still, if you want the most direct route to understanding (some of) lisp's greatness, these two would be it.

      Another possibility is Paradigms of Artificial Intelligence Programming by Norvig. This one teaches you Common Lisp in the introduction, but I'm not sure it's enough by itself for you to really understand some of the later chapters. If you're prepared though (read ANSI Common Lisp first), this book is a gem. It's less about AI than about transforming and optimizing programs. So, code as data.

      You might also look at Practical Common Lisp by Siebel. I haven't read this, but a lot of people liked it, and the code is very real-world (a little dated now, though). It's available online here: http://www.gigamonkeys.com/book/

      In general, you wind up learning a couple of different lisps. Common Lisp and Scheme have the best literature, but the lisp that's most in use today is Clojure. Clojure's macro system is a refinement of Common Lisp's. For learning clojure there are a lot of teach-yourself-X-in-21-days type books. The best of them is the O'Reilly book: https://www.oreilly.com/library/view/clojure-programming/978...

      Scheme doesn't (always) have macros, but I'd be remiss if I didn't suggest something. The Schemer books are some of the most effective pedagogy I have seen on any subject: The Little Schemer, The Seasoned Schemer and The Reasoned Schemer. They are very cute, but don't let that fool you. They get hard (in TLS's case maybe too hard) at the end.

      Finally, there is one of the most important CS books of all time: The Structure and Interpretation of Computer Programs (SICP). It will change the way you think about programming forever. It also explains some important details about how lisp works, and it's so definitive that a lot of them aren't covered elsewhere. ("SICP already did that...") I reread my copy every five years or so, and I always come away knowing something new.

      You don't need to read all this stuff to be a good lisp programmer. One or two of these would probably be enough. But I think it's important to have choices. If you want to talk in more depth, my email's in my profile.

    • chriswarbo 1271 days ago
      Macros are useful for defining things that don't make sense "within" our programming language. For example, I write a lot of Scala code, and that language has a bunch of types like 'Tuple2[A, B]', 'Tuple3[A, B, C]', 'Tuple4[A, B, C, D]', etc. As a programmer we can see that these are all instances of some higher-level pattern 'TupleN[T1, T2, ..., TN]' but Scala has no idea about that; hence we can't write code which, for example, sums the first N elements of any tuple.

      Actually, we can but it's not safe: Scala's tuples implement a 'Product' interface which we can use to loop through the values, but this loses a lot of type safety (looking up values via Product will upcast them to type 'Any'; our function's signature will ask for a 'Product' rather than specifically for tuples of numbers of at least a certain length, etc.)

      Macros generate code at each of their call sites, which lets us automatically translate our 'higher level language' (e.g. 'Tuples of different lengths') into the 'real language' (e.g. Scala). For example, we can have a macro that translates 'tupleSum(5, 10, foo)' into a call like 'tupleSum5_10(foo)', where the function 'tupleSum5_10' is hard-coded to sum the first 5 elements of a Tuple10 (and is therefore acceptable to Scala, since it doesn't try to abstract over tuple lengths), e.g.

          def tupleSum5_10[T1, T2, T3, T4, T5](t: Tuple10[Int, Int, Int, Int, Int, T1, T2, T3, T4, T5]): Int =
            t match {
              case (x1, x2, x3, x4, x5, _, _, _, _, _) => x1 + x2 + x3 + x4 + x5
            }
      
      Of course, we don't want to be writing functions like 'tupleSum5_10' manually. Hence we can use another macro to define these functions! For example, the above might be generated by a macro call like 'defTupleSum(5, 10)'. If we run this macro in a loop, we can define all of the sum functions for tuples up to, say, Tuple20.

      If we provide this in a library, users can treat tuples in a more high-level way. Since each macro call just generates some boilerplate Scala code, the result will be type-checked, etc. to make sure we've not done anything dodgy, e.g. since a call like 'tupleSum(5, 10, foo)' expands to 'tupleSum5_10(foo)', Scala will check whether 'foo' matches the type 'Tuple10[Int, Int, Int, Int, Int, T1, T2, T3, T4, T5]'.

      I think the most famous use of macros in Scala is Shapeless https://github.com/milessabin/shapeless/wiki/Feature-overvie... (although I've not personally used it)

      Note that the exact same problem with tuples happens in Haskell too, which can also be worked around using Haskell's macro system (TemplateHaskell).

  • sahil-kang 1271 days ago
    If you found this interesting and want more, then I think this is a good follow up to read next [1]. It takes a similar approach to introducing the code-as-data idea, but uses xml instead of json as an introductory structure.

    [1] http://www.defmacro.org/ramblings/lisp.html

  • vindarel 1271 days ago
    It's not enough to only talk about syntax when we introduce Lisp.

    The other less known Lisp feature (in the sense of Common Lisp), is the image-based development, which makes for a great REPL. Really interactive, tiny feedback loops, where you compile your program one function at a time. You never wait for a process to restart, even working on a web, GUI or game project. You keep your test objects around. You restart to a clean state when you want. You get compilation and type warnings immediately. You have an interactive debugger that points you to the erroneous line, you fix it, you tell the debugger to try again from the previous stackframe, and you see the function complete.

    Explore! https://github.com/CodyReichert/awesome-cl

  • roenxi 1271 days ago
    The article and the heading seem to diverge a bit. "An Intuition for Lisp Syntax" is much simpler than all the pages: in lisp, the syntax for control structures like a loop is the same as the syntax for everything else.

    The reason for that is because introducing special syntax disrupts useful patterns that could be used for writing completely new control structures. Threading macros, for example, are pretty much impossible to do properly in Python because of syntax irregularities.

    • kreetx 1271 days ago
      Also, haskell has "only" expressions and no statements.
      • chriswarbo 1271 days ago
        Haskell also has definitions, which can't be used as expressions directly, e.g. we can write:

            foo = bar
        
        But we can't write:

            baz = 2 + (foo = bar)
        
        I this case we can get the expected return value by using 'let... in...' syntax, but we wouldn't get the top-level definition, e.g.

            baz = 2 + (let foo = bar in foo)
        
        Likewise for type definitions, class definitions, etc.

        One language which handles definitions nicely as expressions is Nix. Nix has 'let' but I never use it. Instead we can use 'with':

            with {
              foo = bar;
            };
            "hello " + foo
        
        The thing in braces isn't a block of statements; it's a key/value dictionary (Nix calls them 'attribute sets' or 'attrsets'). The 'with...; ...' syntax acts like 'let... in...' but both pieces are expressions.

            with {
              myAttrs = {
                foo = bar;
              };
            };
            with myAttrs;
            "hello " + foo
        
        Interestingly, whilst Lisp distinguishes between 'letrec' and 'let' (bindings with and without mutual/self-reference, respectively), Nix distinguishes between 'rec {...}' and '{...}' (attrsets with and without mutual/self-reference). In other words, when we write 'with rec {...}' the 'rec' modifier affects the definition of the attrset (the '{...}'), it doesn't affect the binding of that attrset into the environment (the 'with'). For example:

            with {
              bar = 42;  // To prevent nonRecursive complaining about a missing variable
              recursive = rec {
                foo = bar + 1;  // The number 6, since 'bar' is taken from this 
                bar = 5;        // The order of definitions doesn't matter
              };  // The attrset { foo = 6; bar = 5; }
        
              nonRecursive = {
                foo = bar + 1;  // The number 43
                bar = 5;
              };  // The attrset { foo = 43; bar = 5; }
            };
            // [] is syntax for a list
            [
              (with recursive;    foo)  // The number 6
              (with nonRecursive; foo)  // The number 43
            ]  // The list [6 43]
        
        Of course, attrsets (recursive or not) can be used as expressions:

            (rec { foo = bar + 1; bar = 5; }).foo  // The number 6
  • wwosik 1271 days ago
    I don't think I like it. I find it hard to quickly skim and find out what each of the elements is, since it can be anything.

    The example amounts to:

      function drawTriangle(left, top, right, color) {
        drawLine(left, top, color);
        drawLine(left, right, color);
        drawLine(top, right, color);  
      }
    
      drawTriangle({ x: 0, y: 0 }, { x: 3, y: 3 }, { x: 6, y: 0 }, "blue" );
      drawTriangle({ x: 6, y: 6 }, { x: 10, y: 10 }, { x: 6, y: 16 }, "purple");
    
    Maybe it's a case of getting used to it, but with my version I can very quickly ignore parts of code, which are not relevant to the thing I'm looking for.
    • culturedsystems 1271 days ago
      Indeed, I think this post ends up being a good argument against Lisp. The supposed negative for JS, as opposed to Lisp, is that "We’d need something like Babel to parse our file, and work on top of the AST to make sure we rewrite our code safely." And? Computers are good at parsing, and adding syntax to a language should be comparatively rare, so why would I choose a language that optimizes for this case, rather than one which optimizes for human readability?

      I think some people will argue that macros shouldn't be rare, that you should define a custom DSL for each application so you can work at a higher level of abstraction. However, macros themselves operate at a fairly low level of abstraction (operating on the AST, rather than closer to the problem domain). I would argue that if you find yourself using macros frequently, that's a sign your language is lacking in higher-level abstraction capabilities.

      • Joker_vD 1271 days ago
        > Computers are good at parsing,

        And humans are even better at it, and actually seem to cope with typographically diverse syntax better than the uniform ones. So it absolutely makes sense to have a nice, heterogeneous, human-friendly syntax.

        As for writing custom DSLs... I always feel vaguely uneasy when I find myself writing, essentially, an interpreter/VM for a simplistic programming language in a form of a set of library routines/components that I then process to use to build my application logic out of. After all, I am already writing code in a rich programming language, why don't I just use it for my application logic in the first place?

        • wtetzner 1270 days ago
          > After all, I am already writing code in a rich programming language, why don't I just use it for my application logic in the first place?

          I'd assume it's because that rich language isn't as well-suited to the problem domain as the DSL. I think one solution might be to come up with a more principled way of writing DSLs, that don't require you to revert to writing an interpreter or compiler "from scratch".

      • wtetzner 1270 days ago
        > Computers are good at parsing, and adding syntax to a language should be comparatively rare, so why would I choose a language that optimizes for this case, rather than one which optimizes for human readability?

        Having everything be uniform has other advantages, but I think they end up being more subjective. Some people like the regularity, because they don't have to remember so many different kinds of syntax. Other people prefer different kinds of syntax, because they find it easier to read. I don't think there's an object measure here, because it all depends on your past experience and your preferences.

    • tonyarkles 1271 days ago
      Going with the example in the article and the nominal purpose of it... how would you take what you’ve written above and parse/execute it without using eval.

      I’m with you, generally. I have used CL off and on for the last 20 years, and when I come back to it it takes a solid week before I start “reading in Lisp”. It definitely doesn’t come naturally when you spend most of your time reading “C-type” syntax all day. But for some tasks it is an absolutely fantastic tool and doesn’t take that long to get back into.

      • bbbobbb 1270 days ago
        Genuine question since I might be missing something: how would you take the lisp version and parse/execute it without using something like eval?
        • tonyarkles 1270 days ago
          I'm not sure if you mean in JS or in CL, but it works basically the same way: deserialize it (not eval) and walk through the array, doing the exact same kind of steps they're doing in the article for the JS version. Build up a symbol table (or two, if your function names and variable names are in different namespaces). The evaluation rules are so simple and consistent that the logic required while walking the array is pretty tiny.

          To do the same thing with Javascript code involves parsing into an AST and walking the that tree; the hard part is that the rules for walking that tree are dramatically more complicated. In the Lisp case, the AST and the original code look very similar; in the JS case, they diverge quite a bit (there's a ton of crazy things in the JS spec if you dig through it)

    • wtetzner 1270 days ago
      I think this actually gives a better intuition for Lisp syntax: https://www.defmacro.org/ramblings/lisp.html
  • userbinator 1271 days ago
    The Lisp still looks like Lisp, but the JavaScript barely resembles what JS looked like the last time I used it heavily (~10-15 years ago.)
  • sriku 1271 days ago
    (shameless plug)

    In Feb 2020, during the first Clojure meetup organized in Chennai, I gave a talk titled "I'm LisP, I'm inevitable" in which I spoke about the journey towards lisp/scheme in muvee's flagship automatic video editing product.

    http://sriku.org/posts/inevitable-lisp/

    It may be helpful to some to understand the recurrent pattern here so they can detect it early when it happens to them.

    • nojvek 1270 days ago
      Watched the talk. The Muvee/MuSE journey must have been fun.
      • sriku 1270 days ago
        It was indeed. It was also nerve-racking in many ways. We were trying to do GPU-based automatic video editing at a time when the Microsoft OSes were evolving rapidly, so were the NVidia and other drivers, and OpenGL itself .. with varied support for "optional" features .. not to mention that machines+OSes weren't yet 64-bit by default. The testing team had a great setup to cover a lot of ground, but there'll usually be something stumping us. It was also a time when people were moving to slower and smaller machines - like mini notebooks - where they'd expect to run muvee on the Intel integrated graphics chip at full screen res. It was also a time when video was graduating to HD and codecs weren't fast enough and 320x240@15fps wouldn't satisfy people any more. We needed 720x480@30fps at least for the previews and taking the GPU route was a no brainer due to that.

        It was a case where we'd raised people's expectations so high (what'd take an editor perhaps a week to do happens in about 5 seconds with muvee) that unreasonable performance expectations became the norm :) You can, in seconds, try variations that would cause an editor to pull her hair. To top this all, the "constructor" which is the heart of the product had to be tested against many variations in media and styles.

        The details of that journey is probably worth another talk on its own.

        muvee released muSE as open source btw and you can find it here - https://github.com/srikumarks/muse . .. and it is (I believe) still usable in the "Reveal Encore" product.

        edit: ... and along the way, muvee also built the first mobile automatic video editor which shipped on the ROM of early Nokia camera phones such as the 6630 (https://mobile-review.com/review/nokia-6630-camera-en.shtml .. scroll down to the "Movie Director" section)

  • zaro 1271 days ago
    I get the code is data thing, but I don't really find it that much useful. And there is a good reason most languages don't allow it - other people :)

    The unless example is a good one. So while you can trivially implement 'unless' in lisp, most probably in a large code base, you'll en up with 'unless', 'if-not' and depending on how creative others are probably also 'negat-if'.

    • Jtsummers 1271 days ago
      You have the same problem in any language when you start scaling up to larger team sizes (or multiple teams). If they don't bother reading the docs or existing libraries, they're likely to reinvent the wheel over and over. The solution is, unfortunately, more social than technical. You have to be deliberate in selecting what goes into the common libraries, and be deliberate in code reviews to make sure junk like that doesn't get created and widely used when an existing solution already exists.
      • Reelin 1271 days ago
        Agreeing, I'd point out that C++ templates are Turing complete (and now they've added constinit and friends). That can certainly result in bad code, but it also enables very good code as well (ex the Eigen library). Python objects can be monkey patched at runtime - a powerful ability, but trivial to misuse.

        Certainly the design of a language should facilitate and even actively encourage writing good code. Attempting to solve systemic organizational or educational issues with it is probably counterproductive though.

        • cloogshicer 1271 days ago
          Since you mentioned the Eigen library: I'm sure it's an extremely elegantly designed library, and I know it's super powerful and efficient. But for someone like me, with fairly limited C++ experience, it was absolutely painful to learn and understand how to use it. Even though it's fairly well documented, I had to really dig through the sources whenever bugs arised. Understanding all these templating abstractions was a huge pain.
    • andreareina 1271 days ago
      'unless vs 'if-not vs 'negat-if is "just" a naming issue, it's the same in any other language wrt functions. IME a bigger issue is jumping between code and data, both for the writer of the macro (viz macros to ensure that a form is evaluated at most once) and the user (is this thing a macro? Is this form going to be evaluated or quoted?).

      The benefit is being able to extend the language. Context managers in Lisp are just macros. Clojure's spec and async are macros.

      For sure, the joke about blowing your whole leg off (as opposed to just your foot) applies, but it's the same for concurrency, distributed systems, cryptography, etc. The answer isn't to ban it, it's to exercise more care in the construction of these abstractions so that mere mortals can use them safely.

    • lawl 1271 days ago
      > most probably in a large code base

      imo this is not only a problem in a singular large code base, but also libraries.

      I know not everyone likes Go, but I've noticed that i find it super easy to dig into some open source dependency and navigate it, because in some ways Go is the exact opposite of lisp. In terms of the language not allowing a lot of flexibility at least.

      I never found navigating foreign java code bases that easy because this project uses spring for dependency injection, that one does some other class loader magic etc, streams, no streams etc. pp.

      That said, I do think LISP is cool, but I think it ends up being a right tool for the job kind of thing. I could imagine LISP being nice for things like game engine scripting etc.

    • Viliam1234 1270 days ago
      > most probably in a large code base, you'll en up with 'unless', 'if-not' and depending on how creative others are probably also 'negat-if'.

      In Clojure, "if-not" is part of clojure.core (the equivalent of java.lang in Java), so a decent programmer would almost certainly be familiar with it; and if not, it would be pointed out at a code review.

      People use Lisp for a while, so the obvious simple ideas were probably already noticed and implemented.

      With less frequent things, implementing the same thing twice under two different names is a problem that happens regardless of the language.

    • Fishysoup 1271 days ago
      From what I understand, this relates closely to the "Lisp Curse"
  • nynx 1271 days ago
    I've never really bothered to learn lisp, but this is incredible. I get it now.

    That being said, I wonder how data types would work as opposed to lists. What would a language based around maps look like?

    • brabel 1271 days ago
      It would look like Lua: https://www.lua.org

      They call "maps" tables. Even arrays in Lua are tables, as are everything else.

      Pretty cool concept... And there's even a low level, statically typed, system language that uses Lua as a composer language, Terra: http://terralang.org

      In Terra, you manipulate your program using Lua at compile time. Really cool concept.

      • zeveb 1270 days ago
        A large difference is that Lua source is not in the form of Lua tables — and that makes all the difference! Having full access to your complete language at read, compile and run times is what makes Lisp Lisp, and that can only sanely be done with a homoiconic language.
    • andreareina 1271 days ago
      Clojure has first-class support for map literals.
    • andyferris 1271 days ago
      Indeed - I have also wondered what a “struct processor” rather than “list processor” would look like.

      I keep thinking code blocks and the struct/map definition could possibly both use `{ ... }`, which might appear a bit like some other languages? What about function calling and definitions?

      The S-Expr thing seems to indicate you’d still need a “head” then a list of key-value pairs?

    • sparkie 1271 days ago
    • garmaine 1271 days ago
      It would look like smalltalk:

      [sendMessage with:arg1 to:arg2]

      • User23 1271 days ago
        Common Lisp has keyword args too.
        • garmaine 1271 days ago
          To be clear this is something I’ve thought a bit about. I think the interesting analog is not maps per se, but relational tuples. Instead of “everything is a list”, it would be “everything is a tuple”, and the environment is a table. Evaluate a tuple to execute, evaluate a table to execute in parallel.
          • User23 1269 days ago
            Objectively speaking table oriented programming is a very interesting topic. Sadly the incredibly effective anti-evangelist calling himself topmind over at the c2 wiki convinced me to completely ignore it for over a decade.

            The relational calculus is an incredibly powerful formalism though, and it's definitely one that has an important place in serious software development.

    • thibran 1271 days ago
      Have a look at Clojure Spec. The other one, which I don't like much, is PHP. For me it's strange that nobody seems to talk about the power of PHP map-like data structure and it's upsides because it is the main data structure.
    • javajosh 1271 days ago
      First of all, a map can be defined as a list where the even elts are labels and the odd ones are values. So it would be a lot like lisp but every elt in every list would have an extra before it, a short string name.
      • brabel 1271 days ago
        It's the other way around, actually. If you define a map as a list, it will have none of the properties of a map: you can't lookup a key in constant time.

        But if you start with maps, then you can represent a list as a special case of a map in which the keys are integers that get incremented, starting at 0.

        • Reelin 1271 days ago
          GP isn't talking about the runtime implementation but rather the serialization format. You can serialize a map as a list. Therefore, a hypothetical Lisp-like language based around maps could simply end up consisting of lists that contain key value pairs.
  • minerjoe 1270 days ago
    My word. Please people, if you haven't programmed a decent sized program in Lisp using an editor tuned to the task, please don't spout off like you know what you're talking about. Just do the work first, it's worth it, just for the understanding.
  • tekacs 1271 days ago
    This is wonderfully written, thank you!

    I'll be sending this to friends who still haven't quite gotten their heads around 'why sexps'. :)

    • stopachka 1271 days ago
      Thank you for the kind words! :)
  • PopsiclePete 1270 days ago
    To me the problem with LISP's was never the syntax, I understand s-expressions and homoiconicity and all that, the problem was also....this is embarrassing...the dev setup/environment.

    In every single non-LISP language/environment - whether it be C, C++, Rust, Go, C#, Java - I can sit down comfortably in VIM or Vscode+VIM and just start hacking away.

    With LISPs, I always feel like I'm fighting my editor - and I am. I know about Emacs+SLIME, I know about paredit, I've seen it in action and it's impressive, I just can't force myself to expand the energy to dive into the Emacs eco-system.

    And yes I've tried vim fireplace and Cursive and VSCode's latest ...whatever.....but they all just feel "wrong" in a way, like a square peg in a round hole.

    I feel if I could just spend enough time to really understand Emacs and all of it's LISP-yness goodness, I'd enjoy LISPs a lot more and use them more in daily work

    • lispm 1270 days ago
      You could use Lisp in a batch style. I would then propose to use a compiler-based implementation like SBCL, since the compiler gives a lot of feedback. That can help in batch programming -> write code, compile it, run it -> with the added error messages from the compiler or the runtime.

      Some people program Lisp with very primitive editors, but they better use the Read Eval Print Loop for interactive programming, too. That would be different from the usual C, C++, ... Java programming. You would need to understand interactive programming. That's a level one needs to master - especially in many situations (and where one does not use SBCL) the dynamic typing of much of Lisp requires interactive exploration/debugging.

  • praptak 1271 days ago
    A simpler intuition. Opening parenthesis moves to the left, whitespace acts as colon.

    So, f(a, b, c) becomes (f a b c).

    • reikonomusha 1271 days ago
      This isn’t the right intuition for the syntax, this is the right intuition for evaluation.

      Why move that bracket? The answer is simple: we want the syntax to also denote a serialized data structure: a tree of atomic elements.

      So (f a b c) represents a list of symbols F, A, B, C, and the interpretation of this list is a call of the function F on arguments A, B, C.

      • Reelin 1271 days ago
        For someone who hasn't used sexprs or looked at an AST before, it might not be clear why a data structure and atomic elements are goals in the first place.

        I tried to write an explanation but found it surprisingly difficult to articulate. In short, there's almost certainly other ways to accomplish the same thing but sexprs are dead simple and they work _really_ well in practice. Just go use them and it will make sense!

        • username90 1271 days ago
          Sexpr makes expressions easier for computers to parse. That is all there is to it. Other languages focuses more on making code easier for humans to parse. Both are good for different things.
          • chriswarbo 1271 days ago
            Lisp and s-expressions are two different things. In particular, there are various alternative syntaces for Lisp, which we can automatically convert between (e.g. via a pre-processor, or a reader macro), e.g

            https://readable.sourceforge.io

            https://www.dwheeler.com/readable/sweet-expressions.html

            https://srfi.schemers.org/srfi-49/srfi-49.html

            https://srfi.schemers.org/srfi-119/srfi-119.html

          • minerjoe 1270 days ago
            But your editor is also a "computer". Editing lisp code with an editor tuned to s-expressions can be pure joy. Most programmers work at the character level of text files, many lispers work much closer to the abstract syntaxt tree (AST).
          • Reelin 1270 days ago
            > Sexpr makes expressions easier for computers to parse. That is all there is to it.

            I would _strenuously_ disagree with that claim, but as previously stated I find the explanation of why I disagree to be surprisingly difficult to articulate. Nonetheless, I find that in practice sexprs are an incredibly effective tool for the task of programming as a whole.

      • Viliam1234 1270 days ago
        > Why move that bracket? The answer is simple: we want the syntax to also denote a serialized data structure: a tree of atomic elements.

        In other words, to evaluate "f(a, b, c)", what you really evaluate is "a", "b", "c", and "f(a, b, c)". You never evaluate "a, b, c". If you move the bracket, then "what is in bracket" matches "what is evaluated".

  • redwoolf 1270 days ago
    The problem I'm having with Lisp isn't the syntax or the language, but the ecosystem. Or rather learning about the ecosystem. There's not much information, from what I've found, about best practices for creating a production ready application. A lot of Lisp tutorials and books extoll the virtues of the language and REPL based development but stop there. For example, I want to figure out how to use Lisp in a CI/CD environment to deploy binaries. Or learn the best practices for creating a project with multiple modules with dependencies.

    Edit: Is this being downvoted because I'm hijacking the thread?

    • slgeorge 1270 days ago
      > There's not much information, from what I've found, about best practises for creating a production ready application

      I have an interest in functional (Lisp) and decided that for me Clojure was a good choice. It was designed to be used in industry so there's people out there who are using it for real applications. Although I wasn't a fan of the JVM I've learnt my bias was wrong and it's given me access to an ecosystem of libraries.

      As I'm more informed now I think that learning Racket, F# or one of the others would also have been fine. But, I don't regret Clojure.

      They're all small(ish) communities so you have to be willing to invest in the community, be willing to put together solutions from bits of information and enjoy experimenting.

      Whether that's the best use of your time is up to you ;-)

  • shaunxcode 1270 days ago
    This is very similar to how I "came to lisp". I was attempting to come up with a more uniform smalltalk/ruby syntax and slowly talked my way into s-expressions. I had tried emacs lisp and scheme so I was aware OF the syntax but the pedagogical experience of WHY the syntax was a lot more profound. After that reading ANSI common lisp, let over lambda, etc. cemented my love for macros and I've never looked back. Actually given that I settled on clojure which still lacks the self-hosted qualities of common lisp I DO look back but only at common lisp.
  • dualscyther 1271 days ago
    I couldn't figure out how to email the author and I don't have twitter, but I found a few mistakes in the "More power" section that tripped me up a bit as I followed along, so just posting here in case it helps anyone else:

    The end of the second last code block should be:

    `data.instructions.forEach(...args.map(parseInstruction));`

    And the full definition of `data.instructions` that demonstrates the use of this modification would be something like:

    `[["rotate", ["rotate", ["drawLine", { x: 0, y: 0 }, { x: 1, y: 1 }], 90], 45]]`

    • stopachka 1271 days ago
      Will look this over and update, thanks!
  • wodenokoto 1271 days ago
    > What if we let our remote user define their own functions?

    Aren't we then back at the problem with using eval?

    • ywei3410 1271 days ago
      No, because you control the base primitive functions by passing in fns. In more formal terms, you can control the context.

      If we really cared, it would be trivial to extend this to a typed variant or change the evaluation semantics. Here's an example in another language [1].

      [1] https://www.youtube.com/watch?v=YxhBxHl76vc

  • frutiger 1271 days ago
    There’s an error/typo in the “Even more power” section. The definition of the “def” function should mutate “variables” not “defs”.
    • stopachka 1271 days ago
      Thanks for the catch! Will update
  • tutfbhuf 1271 days ago
    Since parenthesis are just an arbritary sign, it's possible to replace it with something different that fulfills the same role in Lisp. An example for Lisp without parenthesis:

    https://dustycloud.org/blog/wisp-lisp-alternative/

  • modernerd 1271 days ago
    Is structural editing a genuine advantage over line-based editing?

    The author presents it as such, but their structural editing example shows that it takes three operations to negate a condition (create array, write “not”, slurp forwards).

    That would be one operation in most line-based languages (write “!”), and it also introduces less line noise.

    • vrnvu 1271 days ago
      In a simple example like the author provided maybe is not that obvious but try to imagine an example with a more complex nested syntax and maybe some threading or function composition in it.

      If you have to modify the behaviour of a specific block of code you only need to edit that structure and your block won't get messy neither requiere special syntax.

      As you say, If your "!" transaltes to an expression in your language and you are already writting pure functions that compose niceley then I agree that the benefit would be at a syntax level only, the homogeneity of treating all code (macros or runtime) equally.

    • zozbot234 1271 days ago
      > Is structural editing a genuine advantage over line-based editing?

      A full-featured editor should support both. Line editing is convenient for some editing operations/workflows where you don't necessarily have a well-formed AST at all times. But for many other workflows, structural editing is preferable.

    • chriswarbo 1271 days ago
      Structural editing can be used in many languages, not just Lispy ones (although it's most powerful in Lispy languages).

      I use Emacs to write Haskell, Scala, Python, Bash, etc. and use at least some structural editing operations all the time, e.g. ctrl-right turns '(a, b), c' into '(a, b, c)' and ctrl-left goes the other way.

  • nathell 1271 days ago
  • nojvek 1270 days ago
    One of my algorithmic interview questions is to evaluate mathematical expressions in array of array json (like S expressions). Then add variables and conditions. If time persists, add function declarations.

    I love the idea of data is code is data.

  • masukomi 1271 days ago
    a) great article b) "It can help you move with the speed of a sculptor"... This person has obviously never watched a sculptor. They are so slow compared to basically every other form of artistic visual representation.
    • stopachka 1271 days ago
      I thought this in the back head when I wrote it xD. “hmm...it can’t be too easy changing marble around”. If you have a better metaphor lmk will change :}
      • fishmaster 1270 days ago
        Pottery might be better.
        • stopachka 1270 days ago
          updated! (should show up in a few mins)
  • agumonkey 1271 days ago
    hey, just on mccarthy's paper 60th birthyear. finally :D
  • stevefan1999 1270 days ago
    I dunno if it is appropriate to post a lisp joke here but anyway

    ---

    In the 1960's the KGB was very interested in learning everything possible about the American space program, sending all sorts of spies to find every possible piece of information.

    One afternoon, a breathless spy returned to headquarters with a page of paper in his hand, excitedly shouting to his superior, "Comrade! Comrade! The Americans are using Lisp to write their rocket launching software!"

    The commander was skeptical. "How do you know?"

    "I broke into their research lab and stole a page from the teletype machine! It's not the whole program, but it's the final page and contains the concluding logic of the program! See for yourself!!!!"

    The commander looked at the page and smiled:

    )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))) ))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))) ))))))))))))))))))))))))))) ))))))))))))))))))))))))) )))))))))))))))))))))) )))))))))))))))))))) )))))))))))))))) ))))))))))))))) ))))))))))) )))) ))) )) )) )

    ---

    Jokes aside, I think s-exp is actually a very good way to describe a tree because each nested parenthesis is a nested level in a tree with respect to some, e.g. (a (b (c d)) (e f)) means (b (c d)) and (e f) being a node in a and (c d) in b -> d in c -> f in e

  • trevyn 1271 days ago
    So what does Lisp make harder to implement than today's programming languages?

    It seems like large-scale composition relies on a well-structured way to define and enforce APIs, and oh wow, typing facilities.

    Performance has historically been an issue for Lisp because its model is tightly tied to interpretation, but perhaps modern JIT-style compilation can address this.

    Others?

    What type of issues do large Lisp projects typically run into?

    • Jtsummers 1271 days ago
      Common Lisp has long been a compiled language. Its performance is quite competitive when set against Fortran and C. The idea that it's interpreted (and consequently slow) is a very old myth.

      Of course, it helps to use the right data structures if you want fast Lisp code (using lists for everything is not the right choice).

    • donio 1271 days ago
      > Performance has historically been an issue for Lisp because its model is tightly tied to interpretation

      That hasn't been true since 1962.

    • Reelin 1271 days ago
      > a well-structured way to define and enforce APIs

      I don't see the issue here? What do you think is stopping you from doing this in, for example, Common Lisp?

      > and oh wow, typing facilities

      Common Lisp isn't typed, but there's no reason a Lisp dialect can't be. In fact, Typed Racket is just such a language.

      • Jtsummers 1271 days ago
        Common Lisp is typed. It is not (consistently, see SBCL for a counterexample) statically typed. If you try to do:

          (+ "hello" 3)
        
        You will get an error in CL, and with SBCL an expression like that wouldn't even compile.
        • Reelin 1270 days ago
          Yes, I'm aware (see my nearby comment for more detail).

          I very much enjoy writing Common Lisp, but use SBCL almost exclusively specifically because of the amount of (static) type checking it does. Even then, there are significant limitations that often leave me frustrated in comparison.

      • ywei3410 1271 days ago
        Hell, to go even further you can implement your own type system on top of Racket's macro system; or better yet, use a macro DSL specifically created for type systems to implement your type system. [1]

        [1] https://docs.racket-lang.org/turnstile/

      • flavio81 1270 days ago
        >Common Lisp isn't typed

        It is heavily typed. Very strongly typed for the most part (except numbers). So typed that it won't accept a "character vector" in the place of a string.

        But type checking is mostly at runtime, not at compile-time.

        The good part is that you can edit your program at runtime and restart it at the exact point the error happened, very easily.

        • Reelin 1270 days ago
          In context, particularly given the reference I made to Typed Racket, it should be abundantly clear that I'm referring to the lack of static typing there.

          That being said, I think you'll find truly untyped languages hard to come by. Other than assembly, Forth (similar to assembly in many ways), and esolangs such as Befunge (also quite similar to assembly), languages that see widespread use always check types eventually. Checking types eventually is table stakes. Checking types at compile time, before what you wrote has a chance to explode on you in an unpredictable manner when that one branch for an edge case finally gets taken after a few days of uptime, that's the truly desirable feature that not all languages manage to provide.

  • EE84M3i 1271 days ago
    IMO, by far the most confusing thing about lisp syntax is not code data duality, but instead that when you actually dive into the specs for these languages, its actually still really complicated and full of edge cases. A "toy" lisp can be simple sure, but "real" lisps aren't.
    • ChrisSD 1271 days ago
      The formal Scheme syntax spec is, what?, a few pages. Far smaller than any other "real" language I'm aware.
      • EE84M3i 1271 days ago
        I'm not too familiar with scheme, but syntax seems to be at least a few dozen pages here? www.r6rs.org/final/r6rs.pdf

        Also compare https://people.csail.mit.edu/jaffer/r5rs_9.html and e.g. https://docs.python.org/3/reference/grammar.html which I would consider a relatively syntactically rich language.

        • shakna 1271 days ago
          R6RS was the largest Scheme spec ever, and because of it the next one was actually reduced into two: R7RS-small (core-language) and R7RS-big (extended).

          The R7RS-small standard [0] has already been ratified. It is a similar size to R6RS as a document size, but the semantics are about 8 pages, and include a bunch of macros that you don't need to implement, but just copy and paste into your implementation.

          I'd say it's a similar sized grammar to Python's.

          [0] https://small.r7rs.org/attachment/r7rs.pdf

        • donio 1271 days ago
          What I see is that the Scheme one is a lot more thorough and still shorter.
      • garmaine 1271 days ago
        Real schemes are much more complicated. I think he’s talking about Common Lisp though.
      • Wezl 1271 days ago
        I enjoy lisp/scheme syntax, but I'm especially impressed by lua's simple, readable, flexible, whitespace-insensitive-but-whitespace-friendly syntax. It is a bit more complicated than scheme's, but more intuitive because you don't need to explain quasi-quoting and other lisp-specific stuff. https://www.lua.org/manual/5.4/manual.html#9