A curmudgeon tries a language server(entropicthoughts.com) |
A curmudgeon tries a language server(entropicthoughts.com) |
I would say in my experience we do actually "compile and execute" code, we can just do this incrementally and with less context switching because of the described workflow. But quite often there are in fact separate compile and run steps, it's just not at the whole program level (say, compile a function and run it or a calling function).
And despite how often it's shown off as a strength, redefining code during actual execution isn't quite that common, I think. Yes, it is done sometimes, but lots of programs don't really fit that paradigm in the first place. It can work well for e.g. some games or long running servers.
As for working in an image without source code: this seems like a terrible idea. You might do this for trivial, one off experiments, but in general you benefit from writing out and organizing your code early. I'm not even aware of a particularly easy or clean way to write out source code from an image as described. It could be done, but all the ways I can think of sound like more of a pain or mess than anything. Could just be my experience or I'm missing some neat feature of a commercial Lisp or something.
Some of you may object to the idea that we are super careful with data and modifications to it. I know where you are coming from... however, compared to an image-based system, the modern world is in fact super careful! To the extent that you think we are still not careful enough, that leads you even farther from wanting an image-based system.
If you conceive of a system as the full state space of everywhere it can go, the vast, vast, vast majority of systems have pathological places in it. It is very hard to get them all out. It is advantageous to have a button that says "restart this system from the initial state", and to push it often to make sure it continues to work, even if you're not thinking of it this way. By contrast, image-based systems have the tendency to either 1. get into a bad place in the state space and then the user has no way back out or 2. accidentally create a scenario in which there is no way to bring the system up from scratch anymore, which includes things like "giving the system to someone else to start their own work up".
Sure, it's a good idea to minimize the need to hit the reset button. But you don't really want to give it up entirely.
There are a variety of ways of improving the image-based system. Most, perhaps all of the practical ones, involve basically removing the image-based nature of it and moving closer to the systems we all work with today.
Incremental runtime modification is great for development, but I very frequently restart (from sources) every so often, and certainly at least before deployment to verify everything is working as expected. Sometimes because it's completely necessary, sometimes for my own sanity.
For production, I think it's a very "neat" thing to be able to do and I've heard some great stories (no personal experience), but it's definitely a foot gun if not used very carefully.
The workflow I always use is to write code in my editor and use a keyboard shortcut to evaluate it. Typing directly into the REPL is for one-off state updates, inspection, experiments, and similar throwaway code.
You could also type an individual function into the repl then pretty-print it to a file but that typically expands out any read macros that you may have used.
I think conceptually, Lisp was an interpreted runtime. However, the combination of chasing efficiency with compilation and perhaps "everything is a file" consuming mindshare, interpreted Lisp seems a largely academic pursuit.
It's really great for running agents, though.
This is a strange statement. The manual for Lisp 1.5 describes a compiled language. Lisps have been compiled for over 64 years now using just that version as a baseline. Why do you think Lisp was meant to be interpreted instead?
When stopped at a breakpoint, you can evaluate C/C++ expressions, modify state, call functions, and, with compile code, have GDB compile and execute new C/C++ code in the context of the running process:
(gdb) set variable state->counter = 42
(gdb) call recompute_state(state)
(gdb) compile code
> printf("counter = %d\n", state->counter);
> do_something(state);
> end
That makes for a surprisingly nice "REPL attached to a running C program" experience. You can poke at the heap, change variables, call into the existing code, and inject little bits of new code without restarting the process.One use case I have fond memories of was implementing different trees structure and adding a print_dot function that printed the tree in .dot format and whenever I call the function from inside gdb, the xdot window would redraw with an updated tree graph.
I know it isn't really the same thing as a Lisp image where the injected code is become persistent but still nice to have
P.S: If you are trying gdb's tui for the first time and your program is still laden with print statements, you can ungarble the screen after a printf output with Ctrl+L or the refresh command
Look at the nightmare notebooks you get from data scientists working in Python where they want to have their answers baked into a notebook that they can show to people and don't realize they need to separate code and data in version control and have a script you can run from top to bottom every single time if they want to put their skills on wheels.
To me, I think the smart way to work with Haskell is the exact opposite of iteration. Work it out on paper, refine it, find the algebraic rules for it, refine it some more, and then start writing code once you know exactly what you're doing. I think giving in to the temptation to Just Start Writing Code is how you end up with opaque type spaghetti. That's just my feelings on it.
I don't like them in principle because I believe the language should be more fully integrated with the IDE (like Eclipse does), but they do work, are a step up from syntax highlighting, and are practically a requirement if you have an NxM matrix of languages and IDEs.
The idea of not maintaining properly documented sources would terrify me.
As people point out, if you are not careful and execute arbitrary code in the REPL, your source and image can drift out of sync. When I was working on [1], I would go days without relaunching my image, but all the development was done by evaluating code in Emacs buffers and seeing the results right away. Occasionally, things got convoluted enough that I relaunched the process as a sanity check.
For me, the real benefit of iteratively modifying a live image lies in the sense of flow it engenders. I have not felt that sense in languages other than CL. I sometimes explain the feeling by saying than CL is the only language that I don't feel is fighting me.
> A Lisp programmer does not need to …
Have you even seen R? Jupyter notebooks? The above sounds like a level of insanity beyond that.
The problem with checking things at runtime is that it’s an ever-moving target. Changed this? Now that other thing is out of sync. Changed that? Now the first thing is gone, and it came from far away so you can’t get it back in this session.
I'm "only" using Clojure (which some shall say is not a proper Lisp) and elisp (which is, well, elisp) but I catch every exceptions (including any yet uncaught ones) and I rarely need to restart the app. I've got an helper function to "restart" the app and I can pass a parameter: reset the state of the app or not (usually I don't and keep working with my current app state). As to the main process: I very rarely need to restart the JVM / get a new REPL.
The whole thing is basically a very, very, very long REPL session.
> A consequence of this way of working is that early in a Lisp project, there may not even be any source code to speak of. Instead, the evolving definition of the system exists only in the memory image of the running process and nowhere else.
Yes but be careful... At times this shall bite you: you've got this function or this new definition of that function that you only had working in the REPL and which is not in source code yet. I typically have tests and I run them, from another process, totally unrelated to the one I'm developing in, that I regularly run (not just when I commit): this helps catch at least the most serious "desynch" issues (where the Lisp source code doesn't correspond anymore to what's in the REPL).
> A Lisp programmer does not need to “switch over” to something else because they are already inside their program process. They never “compile and execute” the code because the code is already running and they edit it by hot-swapping code. They never restart in a debugger because they are already inside their program process and can inspect anything they want.
Yup it's all really very sweet. But really: those are all part of that family of languages and not a kludge added later on to the language.
Some people believe that because they have a console in the web dev tools in their browsers that can execute JavaScript code they've got the same thing as a Lisp. It's not anywhere near close to that.
If you’re regularly using a debugger while developing ordinary Haskell programs, you’re doing something terribly wrong. Possibly writing in a very imperative style?
From my time with Haskell, I learned to REALLY think about types. Like, REALLY. Having a type doc above every function that describes a curried path, is really cool. It made OCaml documentation (which I consider among the worst) make sense. (No shade at OCaml as a language. I LOVE it. But their docs aren't gonna win any awards)
Another annoying thing with common lisp is when macros badly expand that can be tricky to debug.
I'm also not a huge fan of quicklisp, though bundles[0] made it a little more bearable for me. You may also consider vend[1], ocicl[2] (heavily AI-assisted recently), or qlot[3].
[0] https://www.quicklisp.org/beta/bundles.html
[1] https://github.com/fosskers/vend
[2] https://github.com/ocicl/ocicl
[3] https://github.com/fukamachi/qlot
For macroexpansion, SLIME and SLY have macrostep functionality that lets you expand macros in place, which has helped me immensely on more than one occasion, though you can also manually expand with macroexpand.Programming is still in this goofy phase as a profession/industry where lots of practitioners interpret suffering and complexity and difficulty as signals of underlying quality.
There are still large numbers of programmers who see advances in human-computer interaction and tools as basically childish, because what matters to them is the feeling of machismo they get while programming, rather than whether their efforts make their users more successful.
See the pushback against IDEs (as you said), syntax highlighting (“syntax highlighting is for children”), Rust (“_I_ know how to manage memory, everyone else is an idiot who needs to git gud”), types generally (“_I_ don’t make type errors”). Even high level languages and garbage collection were met with similar derision when they were going mainstream. There are so many examples of this throughout the history of programming.
If something is hard to do, or resulted in something impressively complex rather than something underwhelmingly simple, it must be good.
Basically, we’re still a very immature profession and it’ll take time before we shed the “the most important thing about programming is that I get to be a wizard” stuff. The users care that the bridge doesn’t fall down when they drive over it. The users don’t care whether designing the bridge made me feel powerful and wizardly because I used a crayon rather than Solidworks.
I hate with passion GUI's that stop be copying text and other crack that modern IDE's often do.
I don't avoid modern features at all and actively use pretty much all of them. If something like and IDE works for you that's great. But their is not some deep psychological need I am fulfilling, other then using tools that have less friction for me.
Additionally the post is about haskell a very high level language with the type system being the main feature, this persons choice in emacs is clearly different to the type that shun type systems and GC. Additionally I do not actually think the types that shun GC and shun type systems intersect as much as you make out, the types that shun GC are performance obsessed kinds of people in which type systems can improve performance significantly and those that shun type systems are (a dying breed) tend to be those who like scripting languages and hacking something together quickly. Most people recognize that both have their place I think.
Maybe the example of a carpenter who has a set of trusted tools and might like or a chefs set of knives, if we really need to look at other professions and measure are self against them. I think if you ask people why they use text editors (or IDE's) they will give their own valid reasons for each.
The behavior also isn't unique to programming at all. You can observe it in the various engineering fields. As a single example, in civil engineering you have the conflict between allowable stress design or load and resistance factor design. You even see the behavior in fields like medicine. Different doctors have different views on different methodologies, treatments, etc.
Ascribing it to the "youth" of programming as a field is strange, not just because of its observability in older professions, but because the things you're complaining about are things that emerged as the field got more mature.
I think there's a serious self-flagellation problem some programmers have for themselves after being talked down to by other fields that you're exemplifying here. There's nothing particularly unique about the social dynamics of programming.
I don’t use LSPs with Haskell anymore. I work on multiple projects which use different versions of GHC and the base library which means having multiple LSPs installed for each one. Trying to get eMacs to reliably detect which project is using which version is not something I have been able to figure out. Plus it’s slow and uses way too much memory.
At current job I’m doing C# and man does the ecosystem there really push you to use an IDE. Most of my org uses Rider. I find it just as fascinating that people actually like using tools like it. Too bad it doesn’t have a decent text editor built in.
I have to agree with your comment about lsp in general they feel a little over engineered or old tags are nicer and feel less bloaty even if less powerful.
Not sure if you are on Emacs 31 yet, but another HNer told me the version issue was fixed for them, but I haven't tried.
> I’m doing C# and man does the ecosystem there really push you to use an IDE.
I had to stop using Emacs for C# dev for a similar reason. Well that, and being on Windows for work only further compounded the issue.
-IDEs are more resources hungry, I remember when trying to use CLion on a big C++ codebase, until increasing the garbage collector maximum heap size all I had was a blank screen, after it was just unusably slow...
-even when IDE work, LSP for C++ codebase aren't very good, for example the caller hierarchy is usually very incomplete..
-I remember having been asked quite a few times, about "how do I use the IDE's integrated debugger to debug a target which was available only through ssh and then you had to telnet from one board to another?" My answer? Use gdb.
That said, I use VSCode now because its multiple tab handling is very nice, but I use it mostly as a glorified text editor (not by choice but because C++ is really IDE's unfriendly).
Sometimes I work on my desktop, but most of my work is on a dev server. Some of my work is on my dedicated server at a budget host. I am not interested in an IDE that runs on my desktop and uses magic to edit files on my servers; properly configuring that with an eye towards security is not my idea of a good time. I'm not going to run an IDE on my server at hosting. I don't want to use multiple editors. The only sensible option is to use a terminal based editor for mostly everything. :p
Having used the same editor for programming for ~ 30 years at this point, I'm pretty good at it. When I use an IDE, it always feels more cumbersome ... sometimes it's valuable because I'm doing unfamiliar things anyway and the contextual assistance is sort of nice, but if I know what I'm doing, it gets in my way.
There's more than one way to do things, and if you want to use all the tools, go for it. I'm not trying to convince you not to, I'm just saying why it doesn't work for me.
If there was a high quality CAD server that was interfaced via plugins for programmable 3D editing software like Blender, then we can start making comparisons. Fact of the matter is that this isn't the case, not like what programmers have gotten in the last 20 years. It's absolutely not impossible for such a thing to exist, it just doesn't. Building things like high quality heuristics for the topological naming problem is quite hard.
It doesn't help that almost every IDE in existence these days is also not a real IDE. Visual Studio, Xcode, Jetbrains, etc. are just a more rigid programmable text editor, they lack the "I", and quite a bit of the "E". Compare them with things like LispWorks and Delphi, and the difference is stark. For the former category of "DE"s, there are often tools which are just as high quality, if not higher quality, elsewhere. Speaking as someone that uses a very wide variety of tools, including the ones mentioned, there's really not a lot of point to them unless you just really like their specific UI/UX.
Didn't Donald Knuth or someone of that era write about this effect in Pascal IDEs?
(The best feature though is that you can run a program containing semantic errors or even minor syntax errors; the body of any function that doesn't compile gets compiled as a throw-exception statement, so you don't have to comment it out when you're working on another part of the code)
My work flow for packaging is very hacky at the moment, I have sbcl install on the host but mostly for developing I run in a podman container and download repos of libraries as I need them and then mount them in a volume and also mount a link farm to load all the asdf systems and that is set up by the initialization script and starts swank and any other initialization needed. probably ocicl does a lot of what I want but for a particular things I was doing I was I also wanted to modify a library so it evolved like this it might be a little insane but new to common lisp development so might be bad.
In other words - if you modify the run time, are you then able to mirror those changes in your codebase and then reboot?
99% of the time, you simply write the code or change in the sources first, then compile/run it in the runtime.
If you're modifying the runtime directly at the REPL without having sources, good luck? or you don't really care about persisting those changes. CL has pretty good introspection, so you could look at the source expression of a function defined at the REPL for example, if you really wanted to recover something (and happened to put the change in a function first), but this isn't always reliable.
So I don't think I'm over my skis here. But I appreciate your clarifying point! I've not read the Lisp 1.5 manual but I probably should!
No reason to leave a mistake (if it was one) or a confusing statement (if it was meant in your, hah, interpretation) unaddressed.
Well, I literally said in my post that basically all Lisp was compiled. And I was replying to a guy saying that I thought all lisp is compiled. So hopefully I'm not contributing to that misconception.
> No reason to leave a mistake (if it was one) or a confusing statement (if it was meant in your, hah, interpretation) unaddressed.
Look buddy, I'm not here for snarking others. I am here to learn and exchange ideas.
Programming is a young field compared to basically all the others. It is immature. It is undergoing massive change quickly. It does have an immature, barely developed theory of practice. This has weird effects, just like how other disciplines had weird quirks when they were young too. What was young medicine like?
I have a great deal of empathy and respect for programmers. Same for engineers and doctors. None are infallible. Doesn’t change my read that our (programming) culture could be better than it is in specific ways, which could result in better decision making in the aggregate.
As for your last point, I agree! I’ve worked too closely with them for there to be any mystique left. I don’t want programming to become civil engineering or medicine but I think we can learn things from how they think about quality and systems. They’ve had more practice at it.
Here's one of the Go creators "explaining" why the Go website lacks highlighting.
https://groups.google.com/g/golang-nuts/c/hJHCAaiL0so/m/kG3B...
https://groups.google.com/g/golang-nuts/c/hJHCAaiL0so/m/E2mQ...
https://www.linusakesson.net/programming/syntaxhighlighting/
The Java and C++ IDEs are separate packages built on a shared base.