Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I 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.



> 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 -- 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.


> 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.


All 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.


> might not be able to achieve performance similar to a "zero-cost abstraction" language like Rust

That might be the case, even though SBCL might have some performance improving features, like a runtime compiler (which can be used to improve performance at runtime) and the capability to implement assembler extensions to the compiler from Lisp.

> 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.

I see no reason, why this should not be useful on the client side, too. Many software systems on the client side have ways to extend them. Doing this in the main implementation language can have advantages, instead of doing it in a limited macro or scripting language.




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

Search: