Hacker News new | past | comments | ask | show | jobs | submit login

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




The first two sound like core parts of Smalltalk as well.


Smalltalk was proudly a descendent of Lisp.


Perhaps I've misunderstood, but several languages have REPLs so that isn't unique to Lisps, is it?


This blog post explains the sort of workflow a Lisp/Smalltalk-style REPL enables:

https://mikelevins.github.io/posts/2020-12-18-repl-driven/


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


Sometimes a break-loop is all you have.

But usually there is something around it. Typically a Lisp system can run several REPLs at the same time, one might be a break loop. You can define a function in another REPL and use it in the break-loop. You can define the function by loading code in a break-loop.

An interaction might be (here in LispWorks):

  CL-USER 6 > (> (sin-x2 3) 0)

  Error: Undefined operator SIN-X2 in form (SIN-X2 3).
    1 (continue) Try invoking SIN-X2 again.
    2 Return some values from the form (SIN-X2 3).
    3 Try invoking something other than SIN-X2 with the same arguments.
    4 Set the symbol-function of SIN-X2 to another function.
    5 Set the macro-function of SIN-X2 to another function.
    6 (abort) Return to top loop level 0.

  Type :b for backtrace or :c <option number> to proceed.
  Type :bug-form "<subject>" for a bug report template or :? for other options.

  CL-USER 7 : 1 > (load "~/sin-x2.lisp")
  ; Loading text file /Users/foo/sin-x2.lisp
  #P"/Users/foo/sin-x2.lisp"

  CL-USER 8 : 1 > :c 1
  T
A function SIN-X2 does not exist. There is an error and we get a break-loop. The break-loop is one level deep. All of Lisp is available in a break-loop: the compiler, the code loader, the evaluator, ... Lisp stays with the break-loop in the context of the error.

I define/write the function in a file and the load the file. Loading the makes the function available in the running Lisp.

Then I use the continue restart to try to find the function again -> the error is gone


In Smalltalk, the code lives inside the image. You can dump it to a file/set of files using "file-out" operation, and import it from files with file-in. But the code is never executed directly from the files - it's parsed, compiled, and executed on read, which creates runtime representation of the code which is saved in the image. When you break into a debugger in the running Smalltalk image, it works on top of the same infrastructure, giving you direct access to the actual code as well as its textual representation at all times. Smalltalk is also highly reflective, so the compiled code (methods) and objects (classes are objects) is available for introspection and modification. Though it's not the best introduction, this post may help get you interested in the paradigm: https://blog.bracha.org/exemplarDemo/exemplar2021.html?snaps...

It's impossible to do with Python, or any other language that wasn't created with image-based runtime. I tried. This approach only works when the "vertical integration" reaches from the very bottom (compilation, stack handling, memory allocation, object creation) to the very top (editing the source, refactoring, test runners, etc.) It's incredibly powerful when it works and is done right. Imagine your whole OS being built around GDB, with extremely late binding of everything and each method being a separate .so/.dll. It would probably be incredibly slow, which is why Smalltalk and Smalltalkers pioneered JITs - but it would also allow you to override any part of your running system while it's running.



My understanding is that you usually write the code in your editor and use an editor command to send the code directly to the Lisp process (that’s what I do, anyway). Or just copy-paste.

I’m not aware of a way to get code back out of the Lisp environment, but I’m fairly new to all this.


That is exactly the clarification I needed. Thanks!


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.


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/


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/


The Julia REPL front end is written in Scheme IIRC


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/JuliaLang/julia/blob/master/stdlib/REPL/s...


also we are very close to moving Julia's parsing (but not lowering) to pure Julia as well.


OK thanks for the correction!


that would suggest it's an "enduring innovation" as per the question


But the previous post argues that the article is about "lisp features that still make it unique".


Forth.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: