reddit / emacs

Repl-driven development for emacs lisp?

FVIIIvWF · 2022-03-26 07:16
Hi all, in an endeavor to truly understand the difference between a REPL and an interactive shell, I've came across this article [On Repl-Driven Programming](https://mikelevins.github.io/posts/2020-12-18-repl-driven/) ([hackernews comments](https://news.ycombinator.com/item?id=25620256)). The author described two important features that Lisp REPLs have and other languages such as Python/Ruby lack. These two features are: 1. **breakloop**. If you call an undefined function, the debugger will be in a **breakloop** (what the L in REPL truly stands for), and in there you can edit the code, without restarting the function call. In emacs if would of be like if we encounter a `(void-variable foo)` error and the debugger pops up. And then we recursively edit the source code and then the debugger will go back one step in the stack and the code runs like no error has happened. 2. **hot reloading**. If you define a class, and instantiate a few instances, changing the class definition while the program is running will dynamic be reflected on the instances without restarting the REPL. In emacs, I feel like the main emacs runtime already has some of this using `eval-defun` or `eval-sexp`, but it's not as automatic as other languages mentioned. --- My questions are: Do we have **breakloop** and **hot reloading** in Emacs? Are there any other tricks that Emacs has for REPL-driven development? Thanks!
SlowValue · 2022-03-26 10:47
> hot reloading Yes this works, see following protocol: (defclass foo () ((a :initform 1))) ; foo (setq hah (make-instance 'foo)) ; #s(foo 1) (slot-value hah 'a) ; 1 (defclass foo () ((b :initform 1))) ; foo (slot-value hah 'a) ;; debugger with error, complaining invalid slot-name a (slot-value hah 'b) ; 1 This works with ordinary `defun`'s too. Emacs is an image based environment like Common Lisp. > breakloop this works too. When in debugger, select one of the presented function calls (aka stack frames) and type `e`, then you can enter valid elisp sexps to run it (try `(print ...)` or `(setq ....)`). But I think you misunderstood something. Imho it is not possible to change the function which is currently running. (This would be like changing the chair you are currently sitting on.) But you can change every other thing (functions, objects, etc), without restarting the whole program. (i.e. changing chairs without leaving the house). And the next time that object/function is accessed the new definition is used. You do that all the time in Emacs e.g. when using `(defun ...)` from scratch buffer or `ielm`. Emacs is imho even more interactive than the regular Common Lisp REPL. It supports `lisp-interaction-mode` which is a REPL on steroids.
wasamasa · 2022-03-26 11:19
To actually answer the breakloop thing, no, you cannot do this. If you inspect the keys in a debug buffer, `e` allows to evaluate a new definition of the erroneous function and `r` allows to return something different. However, if you try to use this, you get "Cannot return a value from an error".
mmontone · 2022-03-26 15:14
Emacs provides a much better environment than just a REPL. It is an [image based environment](https://wiki.c2.com/?ImageBasedLanguage). Open the `*scratch*` buffer: `C-x b *scratch* RET`. You can use that buffer to write and evaluate emacs-lisp code. Enter the definition of a function: (defun hello-world () (print "hello world")) Move to the end of the function, and press `C-m-x` (or `M-x eval-defun`). With that you've compiled your function into Emacs. Now you can evaluate: (hello-world) Move to the end of that expression and press `C-x C-e` (or `M-x eval-last-sexp`) Ok, now let's redefine that function. Edit it and change it to: (defun hello-world () (error "oh no")) Use `C-m-x` (or `M-x eval-defun`) and `C-x C-e` (or `M-x eval-last-sexp`) again. You are popped into a debugger. Note that you are [always in debug mode](https://gbracha.blogspot.com/2012/11/debug-mode-is-only-mode.html) (unless you explicitly disable it); there's no need to switch to a special mode for debugging. As you see, you can make changes live, but you don't need a REPL, you can open any file with elisp code, edit and redefine definitions. This is how you develop in Emacs. You edit files, yes, but redefine and evaluate definitions live. What you have done in the `*scratch*` is what you do in `elisp` files for development. Also, navigation and documentation is supported out of the box: Put the cursor over a function name (like "print" or "error"). Then press `C-h f RET` (or `M-x describe-function`). You get the documentation of that function. Now at that same cursor position, press `M-.` (or `M-x xref-find-definitions`), you are sent to the definition's source code. Lastly, if you want an Emacs REPL, there's one. Run: `M-x ielm` . I don't use it, though; the `*scratch*` buffer is more than enough.
tonicinhibition · 2022-03-26 15:24
I've never been able to figure this out. Are these options generic options for a debugger that is not restricted to emacs-lisp, or are they intended for some other purpose? No amount of exploration here achieves anything useful on my end.
pwnedary · 2022-03-26 21:35
> Move to the end of the function, and press C-m-x (or M-x eval-defun). Placing the cursor at the end should not be needed with `eval-defun`, right? Only if `eval-last-sexp` was used instead.
mmontone · 2022-03-26 21:41
You are right.
ndamee · 2022-03-26 21:53
Why don't you ask about this on the emacs help mailing list? That is read by emacs maintainers too, they can surely answer your question.
7890yuiop · 2022-03-26 23:28
What is it that you can't figure out? `e`? `r`? They both work as described, I believe. `r` is limited as you can only return values at certain times, and you can't return from an *error* (but of course errors are not the only reason why the debugger might be active).
tonicinhibition · 2022-03-27 00:58
No I guess I can't figure out the intention of 'r'. I have never been comfortable with the Lisp debuggers to use them beyond the most basic use-cases - which for me means Emacs opened a buffer due to an error. I have used a proprietary Lisp debugger that was more familiar to me - watch expressions, step in/over/out, etc. In that debugger I have freedom to enter any expression at any time as I'm stepping through. I just don't have any past experience to map onto the 'r' option. Is it to exit the current function with a value? The current line? The current S-expression? Why *can't* I back up and return something sensible when I have an error? That may seem obvious to you but I have no idea how error handling is implemented.
phalp · 2022-03-27 01:09
It's not really helpful to try to pin down what exactly a REPL is versus an interactive shell, or a listener, or whatever you want to call it. The important point is that Lisp is a programming system, rather than primarily a language. That means that a Lisp implementation is a complete, working program, with facilities for incrementally turning it into the program you want to have, as it's running. Lisp source files are a series of commands to the implementation. In a programming language like C, your source is describing your program in a way the compiler will be able to juggle into a runnable program. But in a Lisp program, each defun, each defvar has an immediate, concrete effect on the program's data structures. It hardly even makes sense to ask if Emacs has "hot reloading" because defining functions in a running Emacs is so fundamental to operating the system.
7890yuiop · 2022-03-27 04:47
I don't have answers to all those questions (for actual debugging I usually use edebug instead), but can provide a simple example to demonstrate it working: * Use `M-x debug-on-entry` for some function * Call that function to drop into the debugger at the entry point * `c` to continue to the return point * `r` to change what is returned Use `d` repeatedly in the debugger and you'll be stepping into and out of frames, and will see when values are being returned. AFAIU each frame is always a function call, so the return value is for that function. Reading from `C-h i g (elisp)Debugger` will doubtless help.
tonicinhibition · 2022-03-27 14:34
You've definitely gotten me interested. I'll play around with it today. Thanks!