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