Published 16 Dec, 2019 by Louis Pilfold
Gleam has reached v0.5! It’s quite a bit larger than any of the previous releases, so let’s take a look.
Labelled arguments
When functions take several arguments it can be difficult for the user to remember what the arguments are, and what order they are expected in.
To help with this Gleam supports labelled arguments, where function arguments are given an external label in addition to their internal name.
Take this function that replaces sections of a string:
pub fn replace(string, pattern, replacement) {
// ...
}
It can be given labels like so.
pub fn replace(in string, each pattern, with replacement) {
// The variables `string`, `pattern`, and `replacement` are in scope here
}
These labels can then be used when calling the function.
replace(in: "A,B,C", each: ",", with: " ")
// Labelled arguments can be given in any order
replace(each: ",", with: " ", in: "A,B,C")
// Arguments can still be given in a positional fashion
replace("A,B,C", ",", " ")
The use of argument labels can allow a function to be called in an expressive, sentence-like manner, while still providing a function body that is readable and clear in intent.
One neat thing is that unlike proplist arguments in Erlang or Elixir, Gleam’s labelled arguments are resolved entirely at compile time and have no performance or memory cost at runtime.
A more familiar syntax
One bit of common feedback was that the syntax for enums, case expressions, and blocks felt a little strange or not in keeping with the wider language.
In response to this we have adopted a syntax closer to that found in C-influenced languages such as ECMAScript.
case x {
1 -> "It's one"
2 -> "It's two"
// Braces can be used for multiple statements
_ -> {
let number = int.to_string(x)
string.concat(["I'm not sure what ", number, " is"])
}
}
pub enum Cardinal {
North
East
South
West
}
Multi-subject case
Often it is useful to pattern match on multiple values at the same time. Previously in Gleam the solution was to wrap the values in a struct and pattern match on all values at once.
case Pair(x, y) {
Pair(1, 1) -> "both are 1"
Pair(1, _) -> "x is 1"
Pair(_, 1) -> "y is 1"
Pair(_, _) -> "neither are 1"
}
To remove some boilerplate here Gleam’s case expression now supports pattern matching on multiple values.
case x, y {
1, 1 -> "both are 1"
1, _ -> "x is 1"
_, 1 -> "y is 1"
_, _ -> "neither are 1"
}
Theoretically a case expression can match on up to 16,777,215 values. If you need to match on more values than this please let me know because wow, I wanna know what your code is doing.
Record compatibility
Gleam’s structs are Erlang tuples at runtime. This is great for performance and memory usage but makes constructing them from Erlang or Elixir less straightforward than if they were maps.
To resolve this Gleam’s structs now at runtime have a tag atom in their first position, making them compatible with Erlang records. An Erlang record definition header file is generated for each struct which can be imported into an Erlang project or used from Elixir using the Record module.
// src/cat.gleam
pub struct Cat {
name: String
is_cute: Bool
}
pub fn main() {
Cat(name: "Nubi", is_cute: True)
}
%%% src/program.erl
-module(program).
-export([main/0]).
%% Import the Cat record definition
-import("gen/src/cat_Cat.hrl").
%% Use the Cat record
main() ->
#cat{name = <<"Nubi">>, is_cute = true}.
# lib/program.ex
defmodule Program do
# Import the Cat record definition
require Record
Record.defrecord(:cat, Record.extract(:cat, from: "gen/src/cat_Cat.hrl"))
# Use the Cat record
def main do
cat(name: "Nubi", is_cute: true)
end
end
Anonymous structs
One drawback to structs now being Erlang record compatible is that they can no longer be used to interop with Erlang tuples that do not have a tag atom in the first position.
Anonymous structs map 1 to 1 onto Erlang tuples can are useful for any situation where interop with Erlang tuples is required.
fn main() {
struct(10, "hello") // Type is struct(Int, String)
struct(1, 4.2, [0]) // Type is struct(Int, Float, List(Int))
}
Anonymous structs don’t need to be declared up front but also don’t have names for their fields, so for clarity prefer named structs for when you have more than 2 or 3 fields.
Unqualified imports
Tired of typing module names repeatedly for imported types and values? Well now you don’t have to.
import animal/cat.{Cat, stroke}
pub fn main() {
let kitty = Cat(name: "Nubi", is_cute: True)
stroke(kitty)
}
Project creation
The terminal command gleam new
now accepts a --template
flag to generate
different styles of project. An OTP application template has been added
alongside the existing OTP library template.
gleam new my_fantastic_application --template app
In addition all projects generated by gleam new
come with the required
configuration for CI on GitHub Actions. Push the project to GitHub and your
code will be automatically compiled and your tests run on every code update.
To enable this we’ve created two new GitHub Actions for setting up Erlang and Gleam in your GitHub workflow.
The rest
As per usual, in addition to these features there’s been a number of other improvements to the quality of the error messages, the generated code, and a smattering of bug fixes. 😊
If you want to try out the new version of Gleam head over to the installation page. I’d love to hear how you find it and get your feedback so Gleam can continue to improve.
Want to view some existing Gleam projects? Head on over to the awesome-gleam list. Looking for something to build in Gleam? Check out the suggestions tracker.
Code Mesh 2019
Last month I got to speak about Gleam at my favourite conference Code Mesh! A recording of the talk can found on their YouTube channel:
Thanks
Lastly, a huge thank you to the contributors to and sponsors of Gleam since last release!
- Christian Wesselhoeft
- Guilherme Pasqualino
- John Palgut
- Jonny Arnold
- ontofractal
- Stefan Hagen
- Štefan Ľupták
If you would like to help make strongly typed programming on the Erlang virtual machine a production-ready reality please consider sponsoring Gleam via the GitHub Sponsors program.
Thank you! 💜