Building a language server for Muon
nickmqb.github.io
nickmqb.github.io
I was minded to write an Ask HN related to this. Why do so many new languages launch without what I'd consider to be essential functionality for any language; autocomplete, good IDE integration, intellisense, auto refactoring and navigation support?
I get that all languages are just text at the end of the day but are people really holding the whole API surface and type arguments of standard libraries and 3rd party libraries in their head? Do they remember variable names across files and directories. I feel like my memory must be absolutely terrible because there's no way I could work in a language without decent autocomplete.
In my own little toy languages, integration with LSP is one of the first things I do after I have the basis for a hello world. So, before if statements or loops. It makes a nice feeling to be able to see your language get to look like a professional thing so rapidly. Also I think it helps in developing the compiler, that you know in advance what functionality it needs to have in order to provide good editor support. Also helps guide what unit tests should look like, and helps find bugs because they are apparent as you type.
So we could see an explosion of domain languages, since they are usually more about tooling, and with tooling a nice editor integration is very helpful.
I'm interested in whether the focus on tooling as one of the priorities ended up informing elements of the language design. To take an example mentioned elsewhere, autocomplete is (generally) easier to implement for OO languages than FP ones because the dot syntax for method calls helps scope the options.
Are there instances where you've changed design or felt constrained by making it work with tools?
I agree but most languages are built by single persons or very small teams. They have to select their battles. The last languages developed with that in mind were in order: TypeScript (essentially only for editors), Kotlin (developed by an IDE company) and C# (developed in a HUGE team including a huge IDE team next office door).
But I can understand not wanting to.
I need to go learn an API, and learn how to express the grammar of my language in a new way because I probably can't just reuse the highly tuned recursive descent parser that's already part of the language:
Parts of my language aren't well expressed by some of the ways an LSP is expected to work, stuff inside blocks is only verified as valid, not parsed to check their types or construction. So even a "jump to definition" or "this is clearly an error" is going to involve a lot more work than the interpreter actually does.
Autocomplete itself could be a huge headache due to the highly dynamic nature of the language (variable names can be as dynamically created), without actually running the program, guessing what's inside the environment is going to be incomplete.
In short - editor support is either going to be half-assed (why do it), or more work than writing the language was (tedious).
1) many open-source developers use vim/emacs, maybe something like Sublime Text and don't have experience with IDE offerings for languages like Java/C#. As such, they don't really require much more than syntax highlighting and basic autocompletion.
2) many of the new languages boast powerful metaprogramming capabilities. Metaprogramming makes it very hard to implement working refactoring, because the IDE cannot reason about the code as well as in more 'static' languages like Java.
3) most new languages coming out are closer to the functional paradigm rather than OOP. I think OOP lends itself much more for refactoring features and things like autocomplete.
The primary reason auto complete works better for OO languages is an accident of syntax in the use of dot access on a subject expression.
The only way I could possibly work like that is if my program wasn't bigger than a couple of files not exceeding 50 lines or if I spent all day in the API docs.
It has me worried I've just got incredibly early onset memory loss I guess.
I find myself constantly jumping through the code (with / for find) and I have 3 or 4 terminals open on the same display (to see how things are defined in various files) with stackoverflow a key binding away. I definitely don’t remember how exactly I named my variables but I find that at the same time knowing just the variable name is not enough, I also want to see where and how it’s defined. I use “grep -R” a lot for that as well.
Also often I end up editing files over ssh somewhere so then an IDE also wouldn’t be available, and vim always is.
I’ve never tried tools for refactoring. But as mentioned elsewhere that probably wouldn’t work well for functional and/or niche languages.
I generally feel pretty productive, if I had felt that there is a reward to learning these IDEs I wouldn’t have hesitated to do so.
> a couple of files not exceeding 50 lines
Which in say Stan, might be a complete application, and a month's work, spent mostly wrestling with domain math and algorithm performance.
Versus in say stereotyped industry Java, where it might be a day's work, doing a bit of boilerplate, design patterns, and api plumbing.
Autocomplete is important if it's common to see api names of length 40. Less so if a Haskell-ish 4 is common, and 12 less so.
I wonder if there's an opportunity to better convey the feel of working in various languages and environments. With so many live coding youtube videos, perhaps one could extract gif animations, to illustrate and weave into cross-language stories of best practices and inspiring expertise, of nifty tooling and UX concepts. For some better professional onboarding story than "use a diversity of languages and environments, each for some months or years, and you'll eventually develop a feel for what's possible, for what they each do well".
No. For example, I use emacs, and when I want to use an API for a type I just... grep for the type name, find its file, and skim its APIs, which takes me a couple of seconds but I am able to find what I want in seconds on code bases with multiple millions of lines of code.
Every year people try IntelliJ, or some other IDE, with better auto completion support. After half a day full of getting it to crash, they just go back to vim or emacs, and wait till next year to try these IDEs again.
In fact the first refactoring tools were developed on Smalltalk.
Probably because developing all of that stuff takes a lot of time still, even though it is probably easier now than it was before.
Everything gets done eventually if it’s important, but it may take a while until something gets done even though it is important because there is so much else to do as well.
But it seems to be missing from a lot of languages that launch with the intention of being used for large non-trivial multi-user codebases.
My question I guess was intended less as a criticism of language designers for not including these features and more to find out what I'm missing, that I'm not able to remember the entire (for example) Python standard library, or even the name of methods and classes I previously implemented.
For a long period in the past 15-odd years, new "Web" languages were getting plenty of adoption because the state of the tooling for that segment remained barebones everywhere, with a lot of functionality already in SQL or JS and anything in between being glue, and so competition on language features and syntax took precedent. It's probably in a consolidation phase now - things are getting more exciting in the lower layers of the stack instead.
Because their target audience at launch time is not "end-users", but to create a community to share with the task of language building. And that is a very different kind of project compared to using a language to write applications.
It is not meant to be a functionally complete product, but a demo of what could be the kernel of a really cool product (say, v0.1) -- for purposes of recruiting a team/investment to help carry out that vision and actually ship a useful product (v1.0).
To move the goalposts somewhat I primarily had languages like say Python, Elm, maybe Rust or Go, in mind. I haven't done Python in ages so this is probably an unfair judgement in light of tools like PyCharm but it still seems its supported use-case is text-mode script writing and the ecosystem is still lacking when it comes to large-scale application development. Yet it seems to be far more popular (on here and from a developer happiness standpoint) for application development versus languages which provide tools specialized for this (Java, Kotlin, C#, etc).
Maybe because what 'you consider' is not a universal metric of utility?
I, personally, care not one whit about the useless stuff you mentioned. (Probably because I never programmed in Java and intend to keep it that way while I live.)
And yes, the smart-asses of the 70/80s (aka LISP and Smalltalk) thought about that long time before anyone else. But hey, today's landscape is a bit more complex (think about CSS styles in a HTML embedded in a TypeScript enriched JavaScript JSX file).
The second aspect: LSP is great because it was a cross-vendor, cross-language initiative which had in its first year already two dozen languages on board in addition to half a dozen editors (VS Code, Emacs, Atom, VIM, ...). Emacs plugins could never deliver that because they were bound to Emacs.
I'm not super familiar with SLIME, but that's the goals of LangServ is to consolidate everything into a known protocol. There's also a spec for debugging.
Oddly enough the Wikipedia page for SLIME doesn't seem to mention Racket[0], but it does mention Clojure.
In the end, this is a project that is at least as much effort a creating a programming language.
It is possible, just require major engineering smartness (like writing and improving compilers, code generation, etc).
In fact... Racket has their own UI approach, but I'm not sure if it's just GTK under the hood or what, but it works cross platform.
Projects like sip and PySide show how much effort it takes to make Qt bindings form Python.
And all of this discussion just the bindings, not the actual widget libraries themselves and the effort that goes into those.
But generally, you are right. Language development is expensive enough. UI stack development is in difference to class lib development not a duty of a language developer.
What this article is about is the new(ish) open protocol originally developed for Visual Studio Code that is being adopted by quite a few other editors. The language server protocol (LSP) enables developers to write the kind of tools you're describing and have them support any editor (which has LSP support) on any OS. It's pretty cool. However it's also a fairly involved process writing one so many of the smaller languages haven't yet had the bandwidth to writing their own language servers.
With a REPL or playground, you've got all the libraries loaded, all the function definitions in a map in your interpreter, and if you have a partial identifier you can simply do a prefix search in the interpreter. The interpreter is live; define a new function, and it's added to the map, so it's available for subsequent completions. If the function definition is wrong, hey, it's a REPL, next line try again please.
In a stateless situation, or for a static language, you need to build that map. Ideally you use the compiler's front end, but then the front end needs to be hardened to recover properly from errors. It needs to be told about the cursor location, and when it hits the cursor location in the middle of an incomplete symbol, it needs to figure out what's appropriate to complete at that point based on the stack of scopes available, and jump back out to the editor (or use coroutines, or pause its thread, or whatever) with what it found. It's simply more work. And if you can't use the front end, because the front end wasn't built for it, and you can't modify it, well you're going to have to build what is effectively a new front end to solve the problem.
For those like me who have Vim burnt into their fingers and brain, there is Slimv[1] for Vim. Another alternative is Vlime[2] for Vim. Both these plugins are based on the same client-server architecture that SLIME is based on. In fact, these plugins rely on Swank TCP server (the same thing that SLIME also relies on). Swank receives SLIME commands from Slimv or Vlime and executes them. Of these two Vim plugins, I prefer Slimv personally because apart from supporting Common Lisp, it supports MIT Scheme and Clojure too. Vlime supports Common Lisp only at this time.
[1] https://github.com/nickmqb/muon/tree/master/compiler [2] https://github.com/nickmqb/vscode-muon
for i := 1; i < n {
temp := a
a += b
b = temp
}
> `for` works mostly like in other imperative languages. For convenience, the 3rd term can be omitted; in that case, the index variable is incremented by 1 at the end of each iteration of the loop. The index variable is scoped to the loop body. The variable can be omitted if you want to use an existing index variable, e.g.: for ; i < n; i += 1 { ... }With arrays:
for x in arr {
sum += x
}
for x, i in arr {
sum += x
}
for arr {
sum += it
}
> A secondary loop variable may be specified which holds the index of the current element. If no loop variable is specified, the name `it` is used.[0] https://github.com/nickmqb/muon/blob/master/docs/muon_by_exa...