Caveat: I’m probably a bit of an oddball here in that I don’t really use LSPs because I’ve found them too annoying to set up in the past, and just didn’t find them to be too useful. As long as I have syntax highlighting in Emacs, that’s good enough for me. I think the closest thing to an LSP that I use regularly is Slime, but that is Emacs- and language-specific, and doesn’t use the same protocol.
Anyway… been thinking about this thread all day and thought I’d provide my own thoughts and experiences on some of the questions brought up by Asterite.
Is it mainly the standard library?
The standard library is easily one of my favorite parts of Crystal. It covers enough bases to get me started with most things that I work on, and I like the design of some of its modules (the JSON/YAML parsing especially). There’s always room for improvement and expansion, but it’s definitely one of the nicer standard libs that I’ve used.
Is it being able to prototype without specifying types in methods?
Removing parameter and return type inferencing
This is part of what makes Crystal feel so special to me, and part of the initial appeal that led me to try it out. It can feel like a dynamic language, but I can tighten down the types of variables later on as-needed. This is exactly what I do in the other language I normally work, Common Lisp, which has ways to optionally specify the types of things.
Not having to specify types in Crystal speeds up my coding, especially when doing experiments. But again, I also like that I can specify types in order to catch errors that would otherwise be obscure later on. Like when I wasted three days trying to track a bug down in my port of Doom to Crystal, only to find it was a single typo where I unknowingly switched an int to an int|float union because I didn’t specify a type.
I never realized exactly everything that
require does until I read part 1 of Asterite’s post. I just figured the compiler was smart enough to pull in what a file needed and never considered how it was done. It’s quite nice to not have to specify everything, though.
I don’t compile an entire project from scratch all too often, especially not in release mode. What I instead find myself doing most often is writing a small temporary program in the root of the project, then requiring a specific file from within my source tree to experiment with something within it. This is where
require ends up helping me. It lets me subdivide my program during development.
Is it the concurrency model?
The way Crystal approaches concurrency is quite nice IMO. But it isn’t a game changer for me, and it doesn’t feel too much like a feature unique to Crystal. It simply feels nicely done.
Is it macros and compile-time reflection?
The macro system in Crystal is a lifesaver. The syntax can get a bit unwieldy, at times, as can the error messages, but those are minor issues. This is my other favorite part of Crystal, next to the standard library. Perhaps that’s the Lisper in me speaking, though
Overall, I see incremental compilation as sort of a no-win-but-no-loss thing for me, at least in the way it’s been proposed and discussed. Some of my larger projects (20k - 40k loc, accounting for libraries) could potentially benefit from it, but likely not that much since I don’t actually compile things too often, as mentioned before. What would be beneficial is being able to compile a library into an actual shared library object in a way more akin to C/C++ or .NET. That sort of incremental compilation is something I could get behind, even if it meant trading resulting object code size (I’m already used to binaries that are 50mb in the Lisp world).
Or, if not actual shared libraries, then maybe something where intermediate code gets stored per-source-file in a special binary format together with extra debugging info, and then the compiler can pull things from it as-needed. This is sort of what Common Lisp and Slime do with fasl files if I’m not mistaken, where the fasls store both the machine code and debug info, including location data to do stuff like code lookups. Maybe something along those lines could help with LSP stuff?
But… I hear that shared libraries are probably a no-go. So given that, I think reducing the memory usage of the compiler, or maybe finding more ways to parallelize it, would be a much better way to improve Crystal than incremental compilation.