[HN Gopher] What are the enduring innovations of Lisp? (2022)
       ___________________________________________________________________
        
       What are the enduring innovations of Lisp? (2022)
        
       Author : eslaught
       Score  : 95 points
       Date   : 2023-06-01 17:33 UTC (5 hours ago)
        
 (HTM) web link (elliottslaughter.com)
 (TXT) w3m dump (elliottslaughter.com)
        
       | CalChris wrote:
       | No, Lisp did not invent if statements. They're in Fortran's 1956
       | Programmer's Reference Manual.
       | 
       | http://bitsavers.informatik.uni-stuttgart.de/pdf/ibm/704/704...
        
         | thequux wrote:
         | Fortran 56's IF statement is _very_ different from the Lisp if
         | statement; the former is a conditional jump, and the latter is
         | a structured control flow construct. Further, Fortran 56 's DO
         | isn't a do/while loop as we know it today, but rather a
         | conditional come from. The entire concept of structured control
         | flow was invented by Lisp, and it was very controversial at the
         | time.
        
       | netbioserror wrote:
       | Homoiconicity wins in my book. Clojure is the best proof I have
       | of this: What mattered was not the linked list or the cons cell.
       | What mattered was that all code was just nested calls to
       | functions and macros with the SAME general form: (function arg1
       | arg2 arg3 ...)
       | 
       | Expression-based programming using this core form is trivially
       | easy, and makes immutable data structures downright pleasurable
       | to work with. It also makes code generation easy, though I've
       | never taken full advantage of it; even so, a DSL made of runtime
       | functions is not only possible, but a natural extension of
       | anything you're already doing. Backing those homoiconic
       | expression forms with typical high-performance data structures
       | like arrays and hash maps, as Clojure and Janet do, is a winning
       | formula for making complex tasks simple.
       | 
       | All the patterns of yesteryear for modeling problems begin to
       | look quaint when your program eventually just becomes a pyramid
       | of expression calls, with `main` at the top and database hits at
       | the bottom. The organization of the stuff in between really just
       | becomes a question of standardizing function signatures and
       | organizing modules so humans can easily navigate them.
        
         | JamesLeonis wrote:
         | > It also makes code generation easy, though I've never taken
         | full advantage of it;
         | 
         | I bet you have because Hiccup is an HTML DSL! Compojure is
         | likewise a DSL for building Ring handlers based on HTTP request
         | types.
         | 
         | But to your larger point, I also find I don't reach for macros
         | at all. Looking carefully, I think this is because we have
         | first-class syntax for vectors and maps, and those containers
         | are _very_ flexible about the type of their contents. Combined
         | with the Seq abstraction and a well equipped standard library
         | most Clojure code I write is macro-free.
        
           | maxfurman wrote:
           | Perhaps I'm being pedantic, but `if` and many other control-
           | flow constructs are implemented as macros in Clojure. So you
           | probably use macros a lot!
        
             | bcrosby95 wrote:
             | So everyone uses C.
        
             | [deleted]
        
           | rightbyte wrote:
           | > most Clojure code I write is macro-free.
           | 
           | Conceptually, most forms that are not on the form (fn
           | args...) are macros. You got like cond, lambda and label
           | forms, that are not macros. I don't think Clojure technically
           | has label forms though.
        
         | theLiminator wrote:
         | Definitely, when you can directly manipulate your AST with
         | easy, metaprogramming becomes a breeze.
        
         | gleenn wrote:
         | Very much agree. It also gets you some really useful structural
         | editing capabilities as a side-effect. I can shiffle code
         | around so easily and blindly by just telling it to move an
         | expression right or left, or adjust parens in or out. I also
         | don't have to learn new weird syntax every time the language
         | wants to do something kinda new. So many new Clojure "language"
         | features are actually just libraries which is awesome. Tooling
         | just keeps working and others can provide competing
         | implementations. For instance clojure.spec
        
           | skydhash wrote:
           | This is the best part. Especially with JavaScript with so
           | much difference in runtime and the importance of babel. Even
           | the `loop` DSL can be broken down to basic lisp structure.
           | The flexibility of the language, by allowing itself to be
           | altered, is the most important aspect for me. After
           | completing a first version of a solution, I often find my
           | mind wandering searching for a cleaner, more understandable
           | version of the code. And this is usually done by molding the
           | language to the problem domain.
           | 
           | Something like React in JavaScript exemplifies this. JSX was
           | added for an easier developer experience, but the issue
           | itself is created by the separation of code and data in the
           | language. And CLOS would be perfect for the Component model.
        
         | Blackthorn wrote:
         | Yeah I think this is pretty much the only thing that still
         | matters as an advantage: explicitly writing the source code as
         | a tree, where the left part of the leaf specifies how to
         | interpret the right part. Makes transformations pretty
         | straightforward. Anything else is just window dressing at this
         | point.
         | 
         | Too many people will end up using it to write horrible DSLs but
         | it's the spider man thing I guess. (Please, if you think your
         | problem is best solved by a DSL, reconsider.)
        
         | srcreigh wrote:
         | No better example of this than Racket.
         | 
         | Function application itself is an overridable function. (f 1 2
         | 3) compiles to something like (#%app f 1 2 3). _And you can
         | hook into that_.
         | 
         | Runtime type checking, stack traces, debuggers..
        
           | Blackthorn wrote:
           | You used to be able to do fun stuff like that in clojure but
           | it ended up being axed in the name of performance.
           | 
           | (Well, I guess you still can, but the standard library is off
           | limits now basically.)
        
       | temporallobe wrote:
       | In Clojure, thread macros. Allows very terse but powerful
       | operations on data structures in a very readable way that's
       | easier to reason about than nested calls in C -style languages.
        
       | baq wrote:
       | YAML.
        
       | narag wrote:
       | _...code generation (and therefore metaprogramming itself) are
       | also not fundamentally innovations of Lisp. For example, in
       | C++..._
       | 
       | Wait, what?
       | 
       | Maybe I'm confused but doesn't innovation mean doing something
       | not done _before_?
       | 
       | C++ and even C are more recent than Lisp, they can't be used as
       | counterexamples. Or am I missing something?
       | 
       | (Edit> other than that, I forgot to say: nice article.)
        
         | dmvdoug wrote:
         | I think it's actually a problem throughout most of the article.
         | But I think we can get the point of what the author means:
         | certain programming language features that have enduring impact
         | rose to because they were in Lisp (and Lisp had a period of
         | real prominence). So, they're not strictly speaking innovations
         | of Lisp, but Lisp d was responsible, as a matter of historical
         | fact(ish), for them becoming more widespread.
         | 
         | I say "(ish)" because I very much suspect that if you did
         | actual careful historical investigation of the sources, you
         | would find that there's vanishingly less genuine creation ex
         | nihilo with computers than the standard stories say. Features
         | or ideas that later become prominent, typically seem to be "in
         | the air" or inchoate when the person or people we give credit
         | to for creating them "created" them.
        
       | AtNightWeCode wrote:
       | Useless parentheses?
        
         | WJW wrote:
         | There are many parentheses, but they're hardly useless.
        
           | AtNightWeCode wrote:
           | Love to meet you at a party! ;) Cheers!
        
         | temporallobe wrote:
         | My nickname for Clojure/Lisp is (parenthetical hell).
        
       | Max_Limelihood wrote:
       | This is a really good explanation of why I find Julia
       | (effectively a Lisp in terms of these features) to be
       | indispensable. The ability to generate code on the fly makes life
       | so much easier that I just can't live without it.
        
         | vchuravy wrote:
         | Yeah I often describe Julia as a Lisp in sheep's clothing.
         | 
         | Or as the m-Lisp promised to us :) I chuckled when I read:
         | 
         | > The way that common Lisp systems produce executable binaries
         | to be used as application deliverables is by literally dumping
         | the contents of memory into a file with a little header to
         | start things back up again.
         | 
         | Which is pretty much of Julia's sys-/pkgimages work. Pkgimages
         | are an incremental variation on this idea.
         | 
         | One of the novelties in Julia is the world-age system and the
         | limits on dynamisim it introduces on eval.
        
         | JanisErdmanis wrote:
         | I agree that Julia satisfies the first two properties however,
         | it's not clear how it satisfies the third one (homonicity). In
         | particular, how the argument with regard to Python does not
         | apply to Julia as well?
        
           | adgjlsfhk1 wrote:
           | I think this answer
           | https://stackoverflow.com/a/31734725/5141328 by one of
           | Julia's creators fits here. The TLDR is that homoiconicity is
           | a mix of two separate things: how willing a language is to
           | represent itself, and how close the syntax for code is to the
           | syntax for the data-structure representing that code. Julia
           | meets the first of these, but not the second. Whether this
           | matters depends on why you care about homoiconicity. The
           | biggest difference between Julia and Python here is that
           | Julia has syntactic macros and python doesn't (although see
           | https://peps.python.org/pep-0638/)
        
       | agumonkey wrote:
       | - ~no syntax
       | 
       | - induction
       | 
       | - induction
       | 
       | - some taste for minimalism (although CL/CLOS might feel
       | different back in the days)
       | 
       | - human exploration oriented (repl, mop/updates)
       | 
       | - open homoiconicity, as a programmer lisp is an open box, makes
       | you grow more in depth
       | 
       | - understanding of high and low levels in one place
       | 
       | - radical taste for innovation.. do whatever, you're near free
        
       | drcode wrote:
       | didn't garbage collection originate with lisp?
       | 
       | if so, that should really be considered its most pervasive
       | innovation
        
         | zabzonk wrote:
         | probably so, but surely the most _widely_ used early garbage
         | collector was in line-based BASIC.
        
       | nathants wrote:
       | solving frontend dev in a permanent way.
       | 
       | https://reagent-project.github.io/
        
       | neilv wrote:
       | To anyone not already experienced with Lisps, there's varying
       | schools of thought and opinions on what's important and valuable,
       | within Common Lisp (CL), and within the broader Lisp family.
       | 
       | For example, although this writer doesn't think macros are
       | important, the Scheme (and especially Racket) branch of Lisp ran
       | with macros, then with various other DSL support that take macros
       | further (like Racket `#lang`). Racket also moved towards a strict
       | definition of phases, and a very nice module system that works
       | with that.
       | 
       | That might horrify some CL people, because it moves further away
       | from the dynamic REPL live manipulation strength of CL, but
       | others of us have found the tradeoffs very practical for our
       | needs.
        
         | patrec wrote:
         | Hygienic macros and a strict phase separation are not
         | distinctive to scheme, many languages have this now, most
         | importantly Rust. And just like Rust macros scheme macros are
         | not really an organic part of the language but some extra
         | edifice bolted on top. Scheme definitely deserves credit for
         | pioneering work here, but the only aspect that's of enduring
         | distinctiveness that I'm aware of is Racket's #lang, which
         | basically gives you a less messy and more powerful version of
         | what you could do in Common Lisp with macros and read-tables.
         | 
         | My impression is that hygiene itself (which the scheme
         | community tended to obsess over) is of minor practical benefit,
         | but the fact that you get good error locations (because not
         | using plain lists and symbols makes it easy to carry sufficient
         | contextual information around[^1]) is a major upside.
         | 
         | Out of curiosity, are there additional important practical
         | benefits you see, macro-wise, over Common Lisp (that would make
         | up for the gimped repl)? I.e. in addition to better error
         | messages?
         | 
         | [^1] I seem to remember being told Allegro Common Lisp does a
         | good job here, but I assume identity still imposes some major
         | limitations.
        
           | bjoli wrote:
           | I think hygiene vs. defmacro is pretty simple: syntax-case is
           | explicit non-hygiene where defmacro is explicit hygiene.
           | 
           | I prefer the former, even though syntax case doesn't go far
           | enough. As it is in r6rs bindings are introduced
           | unhygienically within the extent of a macro transformer,
           | which stinks for complex enough macros. Sadly srfi 72 never
           | caught on.
           | 
           | The benefit of the thing giving us the gimped repl is that
           | the runtime can know what something is at compile time.
           | Modules can be compiled with something akin to blocks in
           | SBCL, speeding up procedure calls in ways you can't really
           | achieve with inline caches.
           | 
           | Chez spends capararively very little time worrying about
           | things like that, yet manages to have cheaper procedure calls
           | than SBCL almost always.
        
           | justinpombrio wrote:
           | Rust macros are not hygienic. The biggest issue with this, I
           | suspect, is the visibility limitation: due to lack of hygiene
           | a macro can only refer to a public value. Thus some crates
           | will publicly expose a value with a doc string saying "this
           | is supposed to be private please don't abuse it our you'll
           | clobber the invariants this crate otherwise upholds".
           | 
           | https://doc.rust-lang.org/reference/macros-by-
           | example.html#h...
        
           | DonaldPShimoda wrote:
           | > Hygienic macros and a strict phase separation are not
           | distinctive to scheme, many languages have this now, most
           | importantly Rust.
           | 
           | My understanding is that Rust's macros are only _partially_
           | hygienic. They fall short of Racket 's. To the best of my
           | knowledge, Racket has the most hygienic and expressive macro
           | system of any language today. The people behind it have put a
           | lot of work into it over the past couple of decades,
           | producing more than a few significant papers in the realm of
           | PL research.
           | 
           | > My impression is that hygiene itself (which the scheme
           | community tended to obsess over) is of minor practical
           | benefit
           | 
           | I assume you've not written many macros that generate
           | identifiers before. I assure you, hygiene is quite important
           | for safely reasoning about your syntax!
           | 
           | > are there additional important practical benefits you see,
           | macro-wise, over Common Lisp
           | 
           | Racket sports a focus on what they call "language-oriented
           | programming". The gist of this community philosophy is that
           | all significant software really is an API (or a layer of
           | multiple APIs), and by treating these APIs as "languages" we
           | can make them more ergonomic. Expressive macros enable a
           | style of programming where you can make your API look however
           | you want while still implementing it within whatever other
           | language you're using. Pretty much all of my Racket projects
           | end up with at least a few macros, though it's worth pointing
           | out that the community also stresses that things that can be
           | functions _should_ be functions rather than macros.
        
           | ReleaseCandidat wrote:
           | > Hygienic macros and a strict phase separation are not
           | distinctive to scheme, many languages have this now
           | 
           | Somebody has already written that Rust doesn't. And I
           | wouldn't say 'many', I know of Elixir which does have them.
        
             | patrec wrote:
             | Rust macros are partially hygienic, but even scheme's
             | macros turned out to have unintended hygiene violations
             | (https://okmij.org/ftp/Scheme/Dirty-Macros.pdf).
             | 
             | > And I wouldn't say 'many', I know of Elixir
             | 
             | Rust, Julia and Elixir are three fairly mainstream
             | languages with hygienic macros, and there are many more
             | obscure languages (Dylan, and I believe Perl6 aka Raku) to
             | outright esoteric ones (PLOT), as well as hygenic macro
             | add-ons like sweet.js.
        
         | ilrwbwrkhv wrote:
         | Honestly just using Steel Bank Common Lisp is the best choice
         | if you are looking to build high quality software which just
         | works.
         | 
         | A lot of my software which earns a $4 million profit per year
         | has SBCL sub systems though that is slowly decreasing as we are
         | moving away from Lisp.
        
           | neilv wrote:
           | If you have some great Lisp hackers who are also experienced
           | in industry team software engineering, you step back and let
           | them use whatever they decide is best to use. :)
        
           | valbaca wrote:
           | I'm curious. May I ask what you're moving toward and what the
           | motivations and reasons were? What was great and not great
           | about building on/with Lisp?
        
             | ilrwbwrkhv wrote:
             | I am moving towards Go.
             | 
             | The greatest benefit of SBCL is that it's got great
             | performance and the REPL jack in allows you to debug any
             | application state. Building CL software is just amazing.
             | 
             | So as the company is growing really fast, and I never want
             | to talk to a VC, I need to add those reliability into the
             | system as I can't spend a lot of time training people.
             | 
             | That can only be done by having the highest performance to
             | simplest code ratio (since we lose the repl jack in).
             | 
             | Go is the clear winner here after trying a bunch of them.
             | 
             | It is also easy for people to learn and the amount of
             | tutorials and resources online is great.
             | 
             | It is a bit sad, but the Lisp hacker bucket is a really
             | small pool if you want to hire from so at the end of the
             | day I had to compromise.
             | 
             | Having said that Go is quite a workhorse and has the
             | simplicity of C so it is actually not that bad.
        
               | medo-bear wrote:
               | Are you able tontalk about your product and SBCL's role
               | in it?
        
           | akkad33 wrote:
           | Did you build it on your own? And why are you moving away
           | from Lisp?
        
             | ilrwbwrkhv wrote:
             | Yes I wrote all the first images. I love Lisp and think it
             | is amazing if you approach it in the right way.
             | 
             | Now the company is growing at a rate that I need to hire
             | people and build teams.
             | 
             | That is where Lisp is a hard bargain. The bucket of people
             | who can write a new system from scratch without falling for
             | the common traps is really small.
             | 
             | So that is why we are slowly transitioning away.
        
               | felideon wrote:
               | > Now the company is growing at a rate that I need to
               | hire people and build teams.
               | 
               | And yet you didn't post on the Who's Hiring thread. Not
               | that I'm looking, nor am I not looking, but sounds like
               | an interesting gig.
        
               | BaculumMeumEst wrote:
               | As someone who dabbles in common lisp I would love to
               | know some examples of common traps are in designing a
               | system in cl, since I am probably bound to fall into many
               | :)
        
       | fsckboy wrote:
       | the innovation of lisp led to the innovations of scheme:
       | 
       | Lambda: The Ultimate Imperative
       | 
       | Lambda: The Ultimate Declarative
       | 
       | Lambda: The Ultimate GOTO (Procedure Call Implementations
       | Considered Harmful)
       | 
       | https://research.scheme.org/lambda-papers/
        
         | mcdonje wrote:
         | LLL
        
         | bafe wrote:
         | Indeed,from my understanding of history it seems that lisp
         | brought the idea of homoiconicity through the use
         | S-expressions. Scheme was the first to introduce lexical
         | scoping and in general the idea of constructing the language on
         | a small set of well thought out primitives (see the lambda
         | papers)
        
           | Jtsummers wrote:
           | bafe, I vouched for your comment. You may want to reach out
           | to the mods (hn@ycombinator.com) because your account appears
           | to have been shadowbanned. You only have 4 comments and all
           | were dead (not marked as [flagged] which is typically there
           | with user flags). You may have triggered one of the system's
           | rules which sometimes catch up new users (for instance,
           | creating an account through some VPNs, reportedly).
        
             | dang wrote:
             | Not banned - but yes, subject to extra restrictions because
             | it's a new account. I've marked the account legit now so
             | this won't happen again. Thanks for watching out for a
             | fellow user!
        
               | bafe wrote:
               | Thank you all! I'm a very new user, and perhaps not as
               | good as a writer as most of you, but I am definitely a
               | legit poster, not a spammer
        
               | dang wrote:
               | That's clear, and you're most welcome here!
               | 
               | I'm sorry our software got it wrong in your case, but you
               | should be good to go now.
        
               | bafe wrote:
               | Great, thanks! It's really encouraging to see a community
               | today were the moderators/owners reach out to users
               | directly.
        
       | opportune wrote:
       | The third point is a little convoluted to me and doesn't seem to
       | be a beneficial innovation so much as a design choice.
       | 
       | Really for most of these benefits you can reductively boil them
       | down to "code is data" because serialization-of-code, first class
       | functions, REPLs, etc all more or less follow from that single
       | major innovation.
       | 
       | This single innovation has more far reaching effects than many
       | people realize as once people figured out that code-is-data and
       | AST serialization means little pieces of code (not full programs)
       | could be transmitted and executed over a network, it has enabled
       | massive improvements in data processing through things like
       | MapReduce and distributed databases.
        
       | jjtheblunt wrote:
       | https://a.co/d/6NaRjQG
       | 
       | The "condition system" is niftier than i've seen elsewhere.
        
         | thequux wrote:
         | Indeed. For those who aren't familiar with the concept, you can
         | consider them as the logical conclusion of exceptions.
         | 
         | Traditional error handling, as found in C for example, forces
         | you to handle the error at the moment you detect it. Often,
         | though, that's deep in a library, and what to do about the
         | error depends on the context.
         | 
         | Exceptions allow the a function to declare that when an error
         | occurs in its dynamic scope, it should receive control to
         | handle it. This is, in many ways, a major improvement.
         | 
         | However, consider a program that is parsing a data file.
         | Halfway through the file, it encounters a malformed record. In
         | an exception-based language it would throw an exception,
         | unwinding the stack until you get to the main program logic.
         | However, at that point you've closed the file, losing your
         | position in it and any partially-parsed records. The only real
         | recovery options are to abort reading that particular file or
         | abort the load entirely.
         | 
         | Conditions allow the function that parses a record to declare
         | that it can recover from a malformed record by replacing the
         | binary data with something else, producing an error record, or
         | producing some record that is given from the outside.
         | Similarly, the code that loops over the records can declare a
         | recovery path that skips the malformed record and continues
         | with the next one. Then, when an error occurs, the main program
         | logic can inspect the broken record (possibly by presenting it
         | to the user) and instruct the condition system as to which
         | recovery path to execute. Only _then_ does the stack unwind,
         | and only as much as necessary to get to that recovery path.
         | 
         | In short, exceptions separate detecting an error from handling
         | it. Conditions add a third part, deciding _how_ to handle the
         | error.
        
           | pierrebai wrote:
           | I've read the same, almost word-for-word claims repeatedly,
           | which is annoying given how misleading it is.
           | 
           | Concretely...
           | 
           | To know the context of the conditions, the conditions must
           | give the information. Otherwise, the handler would need to
           | know intimately the implementation details to be able to
           | retrieve the filename, line number, etc. If you can provide
           | the information to the condition you call fill a throw
           | exception with the exact same data. The exception can carry
           | the filename, line numbers, token being parsed...
           | 
           | Second, the example itself is ludicrous. The caller of a file
           | parser providing replacement data for a malformed file? In
           | what world does that _ever_ happens? How could it handle
           | every possible ways a file might be malformed?
           | 
           | Third, in every language, the same can be implemented with a
           | callback. In C++, the standard is now to use std::function
           | for this, which supported free functions, members, lambdas...
           | pretty much everything. The only advantage of List is that
           | the declaration and registration of the callback is a
           | language feature.
        
             | thequux wrote:
             | Yes, you can do this in ways other than conditions.
             | However, I disagree with basically every objection you
             | raise.
             | 
             | First, the decision made by the handler case doesn't
             | necessarily care which file the error was in, what the line
             | number is, etc. All I've ever needed to decide what to do
             | (details below) was the text content of the malformed
             | record. From the perspective of my program, there were only
             | a few possible cases:
             | 
             | 1. The record is malformed in a way that I know how to
             | recover from. In that case, I can just do so and invoke the
             | `use-instead` recovery path. 2. The record is damaged in a
             | new and exciting way. I can log it and try to muddle on, in
             | hopes of catching all the new error cases while I'm off
             | doing more interesting things than waiting on a 6-hour job.
             | 3. The record is damaged irrecoverably and future records
             | depend on it. (e.g., the file structure itself is damaged
             | and this can't be recovered from). This is the rarest case
             | I've come across, but also the only one that's convenient
             | to handle with exceptions.
             | 
             | Further, if it was just a filename and byte offset that was
             | needed to resume where I left off, you may have a point.
             | However, suppose that there was an additional decompression
             | step involved. You can't, with most decompression
             | libraries, pick up decompression in the middle of a stream,
             | at least not without littering knowledge of the
             | decompression through the entire process. Further, bundling
             | everything necessary to pick up the computation where it
             | left off forces you to structure your code in a certain
             | way. For example, packing the state of the computation into
             | a class with member functions doing each part, so that the
             | file, current list of results, etc, are essentially scoped
             | globals. I estimate that this would have been at least 5x
             | more code than what was essentially wrapping a stream with
             | a couple of transformers and iterating over it.
             | 
             | To your third point, I could have structured the parser to
             | call a callback with the details of the problem, which
             | could throw an appropriate exception to unwind to a
             | suitable recovery point. This is, after all, how conditions
             | are implemented. However, conditions as part of the
             | language mean that _every_ error can have recovery paths
             | registered, not just ones that the developers thought to
             | provide callbacks for.
             | 
             | And finally, to your second point. While I originally stole
             | the example from Practical Common Lisp, I've since had
             | exactly this situation come up. I had a ~150GiB file
             | containing, essentially, lines of JSON. My parser validated
             | that the incoming JSON fit a schema and processed it into a
             | more compressed form such that I could fit the aspects of
             | the dataset that I actually cared about into RAM. Now, this
             | dataset had been through several migrations, between a
             | number of different platforms, and not all of the
             | migrations were bug-free. In some cases, it treated UTF-8
             | as CP-1251 and transcoded that into UTF-8. Others got
             | double-escaped. Still others had parts of some fields
             | duplicated in ways that were easy to detect and undo. Some
             | records were just duplicated outright, and some were
             | different versions of the same record. All of these _were_
             | recoverable, but it was 150GiB of data. I couldn 't
             | manually clean it first; I needed to run the program to see
             | what it barfed on in order to fix it. Worse, being JSON, it
             | compressed easily and this was at a time when 150GiB was
             | more than half the disk space I had available to me. So of
             | course the dataset was compressed on disk, and I was
             | decompressing it as I read it.
             | 
             | Now, I'm sure that you can come up with a way that I could
             | have packed the error recovery into the callback in the
             | inner loop of the iterator, but why would I have? I had
             | conditions at my disposal, and the way I actually did write
             | it, I had the happy path in a perfectly clear straight
             | line, and all of the various error cases and how to handle
             | them lined up in a row next to it. The code was easy to
             | read and work with, without any real efficiency cost. The
             | fact that I _could_ have made do with callbacks is no more
             | relevant than that I could have made do using goto instead
             | of loops and functions: we have these abstractions so that
             | we can express what we want our programs to do at a higher
             | level.
        
             | jjtheblunt wrote:
             | > providing replacement data for a malformed file? In what
             | world does that ever happens?
             | 
             | that sort of pattern happens in coding theory, error
             | correcting codes for example.
             | 
             | > In C++
             | 
             | i don't claim to know the answer, but does it matter that
             | the free variable allocation strategy for a Lisp lambda
             | differs from what lambda means in C++?
        
           | Jtsummers wrote:
           | Lisp also has a distinction between kinds of conditions but
           | using the same underlying mechanism. You can have a plain
           | signal which is informational. For instance in a data
           | processing task you can signal your progress, and _if_ there
           | is a handler above it might update some GUI rendering of your
           | progress or print out something to a terminal. If there isn
           | 't, nothing happens. For errors, you can have a handler which
           | will select the restart option or otherwise handle the error,
           | and if there is no handler you'll be brought into the
           | debugger (typically, some modes of execution might cause a
           | program to simply crash/terminate).
        
         | Jtsummers wrote:
         | https://www.amazon.com/Common-Lisp-Condition-System-Mechanis...
         | 
         | Unshortened URL: _The Common Lisp Condition System_ by Michal
         | "phoe" Herda.
        
       | pfdietz wrote:
       | Lisp was a very early, if not the first, case of a language where
       | types are associated with values, not variables. The evolution of
       | implementations of Lisps showed that such languages could be
       | implemented efficiently, even on stock hardware. This last
       | realization took a while (witness how lisp machines were being
       | developed into the 1980s.)
        
       | smegsicle wrote:
       | notably not 'lisp features that have been absorbed elsewhere' but
       | instead the opposite, lisp features that still make it unique
       | 
       | tl;dr
       | 
       | 1. no part of the system off limits
       | 
       | 2. pervasive interactivity
       | 
       | 3. homoiconicity
        
         | Lyngbakr wrote:
         | Perhaps I've misunderstood, but several languages have REPLs so
         | that isn't unique to Lisps, is it?
        
           | schemescape wrote:
           | This blog post explains the sort of workflow a
           | Lisp/Smalltalk-style REPL enables:
           | 
           | https://mikelevins.github.io/posts/2020-12-18-repl-driven/
        
             | Lyngbakr wrote:
             | That is _exactly_ the clarification I needed. Thanks!
        
             | askvictor wrote:
             | Interesting; when you define a function (or anything) in a
             | breakloop, does it get saved to source? (or is there an
             | option to?)
             | 
             | I'm trying to imagine using this style of programming in
             | Python, but the type-redefinition problem is still there
        
           | fsckboy wrote:
           | that would suggest it's an "enduring innovation" as per the
           | question
        
             | Lyngbakr wrote:
             | But the previous post argues that the article is about
             | "lisp features that still make it unique".
        
           | phyrex wrote:
           | There's very rarely any where you can actually interact with
           | the running program. At best you can usually load in a method
           | and poke it, but that's not the same.
        
             | Lyngbakr wrote:
             | So, the REPL mentioned in the article differs from, say,
             | the Julia REPL[0]? Is it simply using the same term for
             | slightly different things?
             | 
             | [0] https://docs.julialang.org/en/v1/stdlib/REPL/
        
               | sundarurfriend wrote:
               | One of the other comments in this thread links to:
               | https://mikelevins.github.io/posts/2020-12-18-repl-
               | driven/ which makes a distinction between "having a REPL"
               | and "supporting repl-driven programming". Modern
               | languages which have a REPL generally don't support the
               | sort of repl-driven programming they refer to. Julia's
               | Revise.jl [1] supports one way of repl-driven
               | programming, and it is in fact the recommended way of
               | doing Julia, but from my understanding it's still very
               | different from the Lisp ways.
               | 
               | [1] https://timholy.github.io/Revise.jl/stable/
        
               | selimthegrim wrote:
               | The Julia REPL front end is written in Scheme IIRC
        
               | sundarurfriend wrote:
               | I believe it's Julia's current default parser that's
               | written in a Scheme, the REPL is a normal Julia standard
               | library written in Julia itself: https://github.com/Julia
               | Lang/julia/blob/master/stdlib/REPL/s...
        
               | adgjlsfhk1 wrote:
               | also we are very close to moving Julia's parsing (but not
               | lowering) to pure Julia as well.
        
               | selimthegrim wrote:
               | OK thanks for the correction!
        
         | amock wrote:
         | The first two sound like core parts of Smalltalk as well.
        
           | SeanLuke wrote:
           | Smalltalk was proudly a descendent of Lisp.
        
       | akkad33 wrote:
       | Which Lisp is the most practical and easy to pick up for a
       | programmer? I already tried emacs lisp but the experience of
       | running emacs was not great, so I gave up
        
         | giraffe_lady wrote:
         | Emacs lisp is one of the worst lisps for most things, though
         | it's a decent fit for its specific purpose.
         | 
         | People are suggesting clojure and clojure is great but it also
         | has rigorous immutability semantics. If you're not familiar
         | with that model you'll spend as much effort learning it as
         | learning lisp, and it'll be unclear which things come from lisp
         | weirdness and which from immutable weirdness.
         | 
         | Someone will also probably suggest racket, which has its
         | strengths as a learning language but is also very large and
         | complex, with numerous extensions to the core language that
         | make it kind of a disorienting ecosystem.
         | 
         | I like janet a lot. It uses "normal" data structures as its
         | primitives rather than the traditional cons cells. So if you
         | want to understand and be connected to historical lisp it will
         | feel very different, and be a poor choice. This also applies to
         | clojure though now that I think of it. otoh if you just want to
         | use parens & prefix notation and play with macros either will
         | work.
        
         | smegsicle wrote:
         | common lisp is the most practical- its an industrial strength
         | application language, gradually typed, very fast
         | 
         | it even includes the most complete oop system known to man
         | which you can completely ignore if you want to and it'll still
         | be the best choice
         | 
         | not that complex either, maybe halfway between lua and python?
         | 
         | there are some rough edges, which comes from being powerful and
         | unopinionated, but they are trivially papered over as you work
        
         | Nihilartikel wrote:
         | Clojure is a serious contender.
         | 
         | To be sure, it's still niche but it has a good ecosystem,
         | piggybacks on mature java libs, and has an active community.
         | 
         | It's thoughtfully opinionated, which I appreciate. Also
         | clojurescript is almost the only front end dev experience I
         | will tolerate.
         | 
         | The hot reloading is magic and it is gangsta as F to use the
         | exact same logic on the fast jvm (or CLR if you're nasty)
         | backend as in the js front end.
        
         | mike_ivanov wrote:
         | What is practical depends on your goals. Clojure can do pretty
         | much everything, though it's a bit less practical for things
         | like high-FPS video games and desktop GUI.
        
           | [deleted]
        
         | ReleaseCandidat wrote:
         | A Scheme, or Racket, which has the most packages of everything
         | not Common Lisp.
         | 
         | I'd suggest Chez Scheme (the Racket fork if you've got an ARM
         | Mac), that is fast and has (real) threads.
        
         | mepian wrote:
         | Common Lisp was created and used by the DARPA and its community
         | of public research labs and private companies to support all
         | kinds of industrial and scientific projects, I don't think
         | there is more practical Lisp. It's fast both in compile time
         | and runtime, supports any programming paradigm you want, and
         | it's one of the most interactive programming environments ever.
         | There are multiple open-source implementations, and there are
         | at least two commercial implementations with paid support.
         | 
         | If you want to start, install the new IDE for Common Lisp
         | called Lem [0] and follow the free online book Practical Common
         | Lisp [1]. If you have any questions, the community can help you
         | on Discord with the language itself [2] and the IDE [3].
         | 
         | [0] https://github.com/lem-project/lem/releases
         | 
         | [1] https://gigamonkeys.com/book/
         | 
         | [2] https://discord.gg/cuVpwZXJ
         | 
         | [3] https://discord.gg/sBYyjyC6
        
         | zilti wrote:
         | My favourite right now is Chicken Scheme. Just the R7RS
         | specification in general; the language is very minimal, yet has
         | basically all you need. And Chicken has awesome C interop and a
         | great little community on IRC in #chicken.
        
       | sbjs wrote:
       | Everything worthwhile about Lisp has been integrated into
       | TypeScript and JavaScript.
        
         | coldtea wrote:
         | Yeah, the same way everything worthwhile about spring water can
         | be found in mud.
        
       ___________________________________________________________________
       (page generated 2023-06-01 23:00 UTC)