A field day for Gleam’s language server | Gleam programming language
Pangram verdict · v3.3
We believe that this entire text is human-written.
AI likelihood · overall
HumanArticle text · 1,466 words · 1 segments analyzed
Gleam is a type safe and scalable language for the Erlang virtual machine and JavaScript runtimes. Today Gleam v1.18.0 has been published. Language server record field support Gleam's focus on static analysis and static types really helps with creating tooling, and that can be seen very clearly with Gleam's much-loved language server. All its features and capabilities can be seen in its documentation. One remaining missing part of it was full support for record fields, a problem now fixed! The language server now supports go-to-definition, find-references and rename for record fields. These work on the field declaration, on labelled arguments, on labelled patterns, on record updates and on record.field accesses, both within a module and across modules. For example: pub type Person { Person(name: String, age: Int) }
pub fn main() { let lucy = Person(name: "Lucy", age: 10) lucy.name // ^ Go-to-definition jumps to the `name` field, and renaming it here // renames the field everywhere it is used. }
Thank you Alistair Smith! People will be very happy for this one! Type variable renaming Similarly, the language server now permits renaming type variables in functions, types, and constants. For example: pub fn twice(value: a, f: fn(a) -> a) -> a { // ^ Rename to "anything" f(f(value)) }
Produces: pub fn twice(value: anything, f: fn(anything) -> anything) -> anything { f(f(value)) }
Thank you Surya Rose! Updating imports for renamed modules Another addition to the language server is support for renaming modules. When your text editor tells the language server that a Gleam file has been renamed, it will find all the uses of that module and update them for the new name. For example: import db_users
pub fn main() -> db_users.User { db_users.new("username") }
Renaming db_users.gleam to database/user.gleam would produce: import database/user
pub fn main() -> user.User { user.new("username") }
Thank you Surya Rose! Pattern match on value code action Flow control in Gleam is done with pattern matching. With how common this is, the language server's "pattern match on value" code action is a great time-saver, quickly inserting a case expression that the programmer can add their logic to. The code action can now be triggered on function calls and their returned values. For example: pub fn main() { load_user() // ^^ Triggering the code action over here }
fn load_user() -> Result(User, Nil) { todo }
Will produce the following code: pub fn main() { case load_user() { Ok(value) -> todo Error(value) -> todo } }
Thank you Giacomo Cavalieri! Faster JavaScript with data singletons When targeting JavaScript, Gleam's compiler outputs straightforward JavaScript code, similar to what a human would write. This gives it performance similar to hand-written JavaScript, which the web framework Lustre has used to achieve comparable or better performance to JavaScript frameworks, such as React. That said, there is always opportunity to improve the code generated by a compiler. Gleam will now identify data structures where all instances are equivalent, and use a single value for each use instead of constructing a new instance each time. Take this Gleam code, for example: import gleam/option.{type Option}
pub fn two_optionals() -> #(Option(a), Option(b)) { #(option.None, option.None) }
You could imagine that previously, that Gleam code would compile to JavaScript code like this (pseudocode): import { None } "../gleam_stdlib/gleam/option.mjs";
export function two_optionals() { return [None.new(), None.new()]; }
While now code closer to this will be generated (pseudocode): import { None } "../gleam_stdlib/gleam/option.mjs";
export function two_optionals() { return [None.instance, None.instance]; }
In our tests this has an impactful improvement to performance, especially to code that uses lots of these data structures such as the view functions of Lustre applications. Thank you Surya Rose for this optimisation! Ambiguous pipe syntax deprecation Gleam's much-loved pipe syntax gives programmers another way to write nested function calls so that they read top-to-bottom and left-to-right. The pipeline one |> two is equivalent to two(one). Most of the time one |> two(three) is equivalent to two(one, three), however if that doesn't result in the correct types then it can instead compile to two(three)(one). This second option means that the pipe syntax is visually ambiguous, and one cannot tell exactly how it compiles without knowing about the function being called. This is not in keeping with the wider language, so the two(three)(one) version has been deprecated and will need to be written explicitly as one |> two(three)(). With this, pipelines have the same rules as use expressions, and the reader can always tell how they compile. We scanned all the publicly published Gleam code and found that the deprecated style is almost never used, but just to help anyone who is using it, the language server now offers a code action to fix the new deprecated style. Thank you Surya Rose! Git dependency path support Gleam's build tool supports adding dependencies from git repositories, which is often useful if there is a package that is not ready to be published, but you still want to try it out. The build tool didn't let you specify a path within the git repository, so if the package you want to use isn't at the root of the repository then you were out-of-luck. To resolve this John Downey has added an optional path field for git dependencies, to specify a subdirectory within the repository. [dependencies] my_package = { git = "https://github.com/example/monorepo", ref = "main", path = "packages/my_package", }
Thank you John! Monorepo fans rejoice! Raising Hex API limits Gleam uses Hex, the package manager for the Erlang ecosystem. It has rate limits that will not be hit in typical use, but can cause problems for folks doing many chatty operations, such as running gleam deps update on many packages within a monorepo. Hex offers much higher rate limits for authenticated requests, so an API key can now be provided to Gleam via the HEXPM_READ_API_KEY environment variable. This will only be used for read operations, so we recommend using an API key that only has read permissions. Thank you John Downey! Hex experience improvements That's not all for Hex, we've also fixed a bunch of sharp edges in the Gleam developer experience when working with the package manager. Moritz Böhme has added a helpful error message for when someone attempts to revert a release that is older than 24 hours and so cannot be reverted. Previously the error from the API would be shown, which could be confusing. He has also improved the error message when failing to decrypt a locally stored Hex API key, such as due to making a typo in the decryption password. Thank you Moritz! Surya Rose has updated the Gleam build tool to use the new Hexdocs URLs, as they recently switched from hexdocs.pm/package_name to package-name.hexdocs.pm for security reasons. Lastly, the gleam hex owner transfer command now takes the username via the flag --user, and the gleam hex owner add command now takes the package name via the flag --package. This should make these commands a little easier to understand. Convert int base code action Like many languages, Gleam supports writing ints in decimal, octal, hexadecimal, and binary. The language server now offers code actions to switch between them instantly. For example: pub fn lucky_number() { 0b1011 //^^^^^^ Hovering this }
The language server is going to show code actions to rewrite it as 11, 0o13, or 0xB. Thank you Giacomo Cavalieri! Generate missing type code action The language server now offers a code action to generate a missing type definition when an unknown type is referenced. For example, if Wibble is not defined: pub fn run(data: Wibble(Int)) { todo }
The code action will generate: pub type Wibble(a)
pub fn run(data: Wibble(Int)) { todo }
Thank you Daniele Scaratti!
In addition to regular double-slash comments, Gleam has triple and quadruple slash comments which are used for writing API documentation. The language server now has code actions to convert between regular comments and documentation comments. For example: // Module description. // Code action available here.
// Comment before function. // Another code action here. pub fn wibble() { // No code action here. todo }
/// Doc comment. /// Another code action here. pub fn wobble () { todo }
Triggering the code actions in all of these places will result in: //// Module description. //// Code action available here.
/// Comment before function. /// Another code action here. pub fn wibble() { // No code action here. todo }
// Doc comment. // Another code action here. pub fn wobble () { todo }
Thank you Daniel Venable! Redundant alias fixing Gleam's automatic code formatter will now remove redundant import aliases that have no effect on the code. For example: import wibble.{Wibble as Wibble}
The name and the alias of the imported value are both Wibble, so the formatter will remove the alias like so: import wibble.{Wibble}