3 ms·
> Three things are killer features for me with the slime+emacs running sbcl. First is that you can see the underlying source for anything down to the lisp imple
by winter_blue 2y ago
> Three things are killer features for me with the slime+emacs running sbcl. First is that you can see the underlying source for anything down to the lisp implementation, e.g. the definition of 'macrolet'.
If you’re working on a project (on its code), then you can (obviously) see its code as well.
Statically typed languages make exploring the code a lot easier, especially with an IDE which leverages the type information to assist in code exploration.
> The second thing is if there is an error, it displays the stack with the ability to see what the local variables are in any function on the stack.
This works with debuggers (and is easy with an IDE) in most popular languages.
> The third thing is that one can patch a running program, either once a breakpoint ('break) is encountered, or by doing control-C at the repl. And then resume it.
You can do this with most major languages today as well.
- wglb 2y agoI was likely not clear in my explanation. These three things are all done from the REPL. I don't have any experience doing C or C++ from a REPL. I disagree that "Statically typed languages make exploring the code a lot easier." I'm not aware of an equivalent in C or C++ to the "esc-." method of going directly to the source. The keystroke combination to find the definition of a function or symbol using slime and emacs is "esc-." That takes you right to the code. If it is a symbol defined by Lisp, you see the underlying code in the actual source for Lisp. So the second thing is also done when running a program from the REPL. Once tripped, you can expand each element in the stack to see and inspect the variables local to that function. You can then edit the source for that function, compile only that function in place, then resume execution. I admin that I don't know how to do that in C or C++. My explanation should have referenced the REPL, which is key to these.
- winter_blue 2y ago> Once tripped, you can expand each element in the stack to see and inspect the variables local to that function. When a breakpoint is tripped, with most modern IDEs and languages, you can see the local stack, global state (and pretty much all state), and this state is presented in a UI with dropdowns, etc. Debuggers in modern IDEs also let you evaluate an expression in place, where the breakpoint was tripped, so you can further dig into the local state with a debugger. This sort of works like a REPL (but you're "sort of" limited to a single expression -- but that's something you can still circumvent with an immediately-called lambda). >You can then edit the source for that function, compile only that function in place, then resume execution. This is supported by some languages, but admittedly support for this varies widely, and sometimes can be weak or finicky. For example, the JVM family of languages have a feature called HotSwap. This feature is quite old: https://www.jrebel.com/blog/java-hotswap-guide https://www.jrebel.com/blog/java-hotswap-guide -- it was introduced in 2002 with the Java 1.4 JVM. I use it even today, with personal projects, where I update the code of a running server live (without restarting the server). HotSwap unfortunately sometimes is broken with certain major JVM frameworks, but for my personal projects, I've always made sure to only use libraries that work well with HotSwap, since I consider live code patching of a running server to be an absolutely important feature. >the "esc-." method of going directly to the source To be honest, I'm not entirely sure what you mean here. When I'm working on a project (as in, I have an IDE open for it, and I'm writing the code for it), all the code is right there. When I use a debugger, I can see the stack of all the ancestral functions, and with a single click I can go to the definition of any function. Static typing basically makes the "Go to definition" more precise, as it always jumps to the place in the code (file & line) where any particular function or value is defined. With dynamically typed languages, an IDE can sometimes show you multiple option when there's multiple functions with the same name defined in different places. >These three things are all done from the REPL. I don't have any experience doing C or C++ from a REPL. Most of the features I've described above work with C and C++ IDEs (like CLion, Visual Studio, etc), except for the live code hot swapping -- I've only used live code patching with JVM HotSwap with Java, Kotlin, etc.
- lispm 2y ago> Most of the features I've described above work with C and C++ IDEs In Common Lisp you won't need an IDE and you won't need to instrument the application with a debugger, because much of the features are built into the language runtime. For example Common Lisp has an error handling system with resumeable exceptions. Another example is the object system where the objects are updated on changes. For example I can load a new class description and the existing objects are changed to the new description: new slots, fewer slots, new superclass, removed superclass, ... This makes hot loading much more useful. Additional many calls to global functions are late bound, which again makes hot loading more useful. A typical runtime also includes a code loader, a compiler and/or an interpreter, which makes updating code easy. All without an IDE or a foreign debugger. This also means that when for example the resident compiler is called, the compiler is a part of the runtime. An error under compilation, will show me the compiler in the stack trace and the compiler itself can be live edited/updated, while I'm in a compilation error. The effect is then, that by default programs are running inside a runtime, which always provides the features of interpretation, compilation, code loading, repls, debug repls, runtime type checks, OOP updates, ... One would need to do something to turn those things off. In a typical C/C++ program, the compiler, debugger, IDE, linker, ... are not a part of the program's runtime.
- winter_blue 2y agoAll of this late binding, and needing a runtime, seems to suggest even a JIT LISP implementation (e.g. SBCL) might not be able to achieve performance similar to a "zero-cost abstraction" language like Rust. When you're in debug mode (e.g. with C++ or Rust or a JVM language), there's a bunch of debug info added to the binary plus various optimizations are turned off. I'm sure some optimizations that C++ / Rust / LLVM use involve entire eliding classes and other abstractions, so that an "O3" release-mode binary doesn't even contain any cruft. There's an argument to be made for this approach, at least for software that's run on customers' computer. For server side software, I can see why having always-available debuggability is useful.
- lispm 2y ago
- lispm 2y agoCommon Lisp also provides type information, dynamic type information. CL-USER 72 > (defclass person () (name age)) #<STANDARD-CLASS PERSON 80101598FB> I can now ask Lisp to find the class object named person. CL-USER 73 > (find-class 'person) #<STANDARD-CLASS PERSON 80101598FB> Now I've got a real class object, not just a pointer to dead code. I can inspect it, change it... I'm adding a slot to an existing class: CL-USER 74 > (defclass person () (name age height)) #<STANDARD-CLASS PERSON 80101598FB> I can then ask for the slots of that class. CL-USER 75 > (class-direct-slots (find-class 'person)) (#<STANDARD-DIRECT-SLOT-DEFINITION NAME 8220278CA3> #<STANDARD-DIRECT-SLOT-DEFINITION AGE 8220278CBB> #<STANDARD-DIRECT-SLOT-DEFINITION HEIGHT 8220278CD3>) I can define a method for it. CL-USER 76 > (defmethod greet ((a-person person)) (format t "Hello ~a" (slot-value a-person 'name))) #<STANDARD-METHOD GREET NIL (PERSON) 801001B69B> I now can retrieve the method object from its generic function. CL-USER 77 > (find-method #'greet () (list (find-class 'person))) #<STANDARD-METHOD GREET NIL (PERSON) 801001B69B> That's live code exploration using dynamic types. > You can do this with most major languages today as well. Hmm, I'm in this graphics program on my Mac, how do I "break" the program and then interact with its Objective-C objects?