Introduction

Gleam is a newish programming language that I recently stumbled upon. It is so little-known that there aren’t really any tutorials available on it (even on the official documentation), so I decided to write my own.

Gleam transpiles to Javascript and Erlang. I’ll be focusing my attention on the Erlang side of things, as it is more mature. And to be honest, I don’t feel any reason to replace Rescript, my go-to transpires-to-Javascript language.

Table of Contents

Patreon

If you find my content valuable, please do consider sponsoring me on Patreon account. I write about what interests me, and I usually target tutorial style articles for intermediate to advanced developers in a variety of languages.

I’m not sure how far I’m going to go with this series on Gleam, so if you’re particularly interested in the topic, a few dollars a month definitely gives me a bit of motivation to keep it up.

Gleam In Brief

Gleam self-describes as “Safe, Friendly, and Performant”, but what language doesn’t describe itself that way? It also mentions “Erlang compatible”, which narrows it down a bit, though syntax-wise, they don’t look much alike.

A more informative description might be “Soundly Typed, Concurrent, and Functional”.

Gleam fits in the bucket of what I call “new-style functional language”. Like Rescript, it is more approachable (“friendly”) than older functional languages such as OCAML and Haskell. At first glance, it also looks more practical and pragmatic, but I haven’t used it enough to be sure of that.

Like Erlang, Gleam uses the industry-proven actor model for concurrency. Surprisngly, very little of this model is encoded into the language, instead being farmed out to libraries, which is why it is able to build to Javascript without shipping a massive runtime.

Sound interesting? I think so too. Let’s build something together.

That’s right. I’m not an expert in Gleam. I learn best by explaining things, so I’ll be learning along with you. I do assume you have some programming knowledge. I’ll probably drop comparisons to other languages (I mean, I’ve already done so, so why stop now?) all over the place, but you don’t need to know any specific one of them to understand my content.

Install Gleam

Getting programming environments set up can be a real pain, especially with languages that haven’t matured their packaging ecosystem. The Gleam Getting Started page has a nice description of how to get up and running, and all of the methods look civilized in a “welcome to our neighbourhood, we’re glad you’re here” kind of way.

For my part, brew install gleam was all I needed.

Hello Gleam

Fire up a terminal and point it to your hobby code directory. If you don’t have a hobby code directory, make one.

“Everybody needs a hobby code directory.” – definitely not my mother.

Gleam ships with its own template generator, and I’ll give you three guesses as to how to start a new project (no peeking!):

gleam new hello_world
cd hello_world

Someone recently complained to me that “Hello World” is a stupid custom. But I’m not about to fly in the face of decades of programming tradition, and the truth is I can get you to “Hello World” without writing a line of gleam code. Don’t believe me? Try this:

gleam run

Does it say Hello from hello_world? Why yes, I believe it does. I’m definitely not taking credit for all the hard work the Gleam devs have done for us here. Let’s take a look at the files generated for us:

.
├── README.md
├── gleam.toml
├── src
│   └── hello_world.gleam
└── test
    └── hello_world_test.gleam

Four files. That’s comfortable; enough to get us started, but not so much that we can’t remember what’s been written for us and spend our first ten minutes deleting useless boilerplate. (That sentence was not a dig at create-react-app, but this one is).

We’ll ignore the README (does anybody ever obey that filename anyway?) and gleam.toml. Let’s look at that source file, though.

// hello_world.gleam

import gleam/io

pub fn main() {
  io.println("Hello from hello_world!")
}

Kinda interesting that you have to import a module just to print something to the screen. It’s not a common design choice these days, but I like it; it’s nice and explicit.

Imports look about the same as any other language (I’m not going to try to figure out the Gleam module system at this time).

pub fn means we are defining a public function (gleam functions are private by default). The function has to be named main in order for the runtime to pick it up.

Let’s also take a gander at that auto-generated hello_world_test.gleam file:

// hello_world_test.gleam
import gleeunit
import gleeunit/should

pub fn main() {
  gleeunit.main()
}

// gleeunit test functions end in `_test`
pub fn hello_world_test() {
  1
  |> should.equal(1)
}

I like it when a language puts testing front and centre. Encoding it in the template is a good start. Again we see a couple imports. There is a main function, which acts as the entry-point for the file, but all it does is farm the real work out to gleeunit.

On Standard Libraries

Here’s an interesting thing: what is gleeunit and where is its home? You might guess, as I did, that gleeunit ships with Gleam, but you’d be wrong as part of the standard library. In fact, the gleeunit repository is not even maintained by the official Gleam organization (but the maintainer, Louis Pilfold, is the inventor of Gleam, so what difference does it make?)

Turns out, gleeunit was available to our program only because it is specified in the gleam.toml file:

name = "hello_world"
version = "0.1.0"

# Fill out these fields if you intend to generate HTML documentation or publish
# your project to the Hex package manager.
#
# licences = ["Apache-2.0"]
# description = "A Gleam library..."
# repository = { type = "github", user = "username", repo = "project" }
# links = [{ title = "Website", href = "https://gleam.run" }]

[dependencies]
gleam_stdlib = "~> 0.20"

[dev-dependencies]
gleeunit = "~> 0.6"

This file clearly looks like somebody thought Rust’s Cargo configuration was a good idea (it is) and decided to copy it.

Note that not only is gleeunit specified as a dev-dependency, but even gleam_stdlib is a dependency. So unlike virtually other programming lanuage I’ve ever seen, Gleam doesn’t ship with its own standard library. Seriously. Comment out the gleam_tdlib and gleeunit lines by adding a # in front of them and run gleam run. Check out that error:

❯ gleam run
  Compiling hello_world
error: Unknown module
  ┌─ ./src/hello_world.gleam:1:8
  │
1 │ import gleam/io
  │        ^^^^^^^^

No module has been found with the name `gleam/io`.

When you think about it, it kind of makes sense. The Gleam standard library that is used when you build for Erlang is probably going to be totally different from the one that ships with Javascript. Unless you want to bundle an entire Erlang runtime with your Javascript code, and why would you ship a virtual machine when the browser already provides you a perfectly serviceable virtual machine.

(Please ignore how I’m carefully not ranting about the fact that modern browsers feel a need to ship two virtual machines. It’s not important.)

(BTW, Isn’t it amazing what you can sneak into a parenthetical?)

Two Ways To Call Functions

Gleam is a functional programming language, which means almost all you’ll be doing is passing values into functions. There’s no objects or methods or anything; just (immutable) data and the functions that operate on them.

We’ve seen three functions so far, two of them named main, and another named hello_world_test. We’ve also seen three separate lines of code that call functions:

gleeunit.main()
io.println("Hello from hello_world!")

and

1
|> should.equal(1)

The first one is pretty straightforward and looks like most common languages. A module name is specified followed by a function in that module, and empty parantheses to indicate that there are no arguments.

The second one may also look pretty familiar; it’s the same general idea, except there’s a single argument inside the function.

You might be used tho the third if you know other functional languages, but I’d personally never seen anything like it before I started working with Rescript.

|> is called the “pipe operator”, and it behaves similarly to the | unix pipe symbol used to connect outputs to inputs in terminal shells. I always read it as “out to” in my head.

So in the example code, the 1 value goes “out to” the should.equal function. But what does it mean for a value to go out to?

Turns out it’s not terribly complicated. |> means “insert this as the first argument”, so if “out to” doesn’t work for you, you can always think “becomes the first argument of”.

Two examples should eliminate any confusion:

  io.println("Hello from hello_world!")
  "Hello from hello_world!" |> io.println

The two lines above do exactly the same thing. They pass one string argument into the io.println function. The following two lines are also equivalent. They pass two integer arguments into the should.equal function:

  1 |> should.equal(1)
  should.equal(1, 1)

You’re probably asking yourself why you need two syntaxes, or indeed, which one you should choose when. The first question is answered by an example from the Gleam documentation:

string
|> string_builder.new
|> string_builder.reverse
|> string_builder.to_string

is rather easier to read than

string_builder.to_string(iodata.reverse(iodata.new(string)))

The second question is more nuanced. Programming is an art, and the decision as to whether to use pipe-first or call syntax is up to the poet. Generally, it depends on the symmetries in the particular piece of code you are writing.

My general guidance is that if you can use two or more pipe operators, you should use two or more pipe operators. But if your function would only have one pipe operator, it depends on how much importance you want to give each argument. In the should.equal case, the value being checked is of more interest than the expected value it is being compared to (not to mention that 1 |> should.equal(1) reads from left to right like standard English).

In other words, just be cautious of what you sneak into parentheticals.

Let’s Write Some Code

Gotta love it when a programming article is approaching two thousand words and you haven’t done any programming yet, eh? At least you know two ways to call functions already!

“Hello world” is always your first app, and “Hello ” is always the second one, right?

So let’s add a variable to our program and practice those two ways to call functions:

import gleam/io
import gleam/string

pub fn main() {
  let name = "Traveller"

  string.append("Hello, ", name)
  |> io.println
}

Run this with gleam run and you should see a nice greeting. If you haven’t travelled much lately (who has? 🙁) you might want to change the value of the name variable.

Well, I say variable, which just shows how used to imperative programming I am. In fact, all values in Gleam are immutable. They never change. So there’s nothing variable about it. The correct term is “binding”. We are binding the value "Traveller" to the name name.

We then call the string.append function, passing it two arguments: the string "Hello, " and the value of name. This function returns a new string, which happens to be "Hello, Traveller". Then we pipe that new string into the io.println function we are already familiar with.

Note that this could also be written as:

import gleam/io
import gleam/string

pub fn main() {
  let name = "Dusty"

  "Hello, "
  |> string.append(name)
  |> io.println
}

but it looks weird to me because it gives me the sense that "Hello, " is more important than name, when really it’s the result of concatenating them that is interesting.

We could also have written it this way:

import gleam/io
import gleam/string

pub fn main() {
  let name = "Dusty"

  io.println(string.append("Hello, ", name))
}

which looks familiar to me as a coder of many other languages. It gives a sense that the println function is more important than the appended "Hello, Traveller" string, though, which doesn’t feel quite right.

More importantly, this variation didn’t allow me to demonstrate the use of pipes and call syntax at the same time. Pedagogy wins again.

tl;dr write whichever syntax looks nicest to you.

A Brief Note on Strings

Like any modern language, strings in Gleam are UTF-8 encoded binaries and can contain any valid unicode. Try this:

import gleam/io
import gleam/string

pub fn main() {
  let name = "⛄"
  ["Hello, ", name, "! 🎉"]
  |> string.concat
  |> io.println
}

In addition to a couple unicode characters, this snippet introduces us to lists. We made a list of three strings, piped them into the string.concat function, and piped the result into the io.println. (In this case, I preferred pipes because the list of strings deserves a prominent place at the front of the line).

Unlike most imperative languages, Gleam lists are linked lists rather than array lists or vectors. This is common in functional languages, and you’ll find that you use them a little differently. I’ll probably go into more detail in a later article.

Read The Name from Standard Input

It wouldn’t be very convenient for users of our program to have to edit it with their own name and rebuild it. Let’s instead read the value from standard input.

Surprisingly, there is no entry in the Gleam standard library for reading a line from standard input. It’s possible this is just an oversight, but I suspect it’s because the gleam maintainers are trying to keep the standard library language agnostic. An io.read command that depends on Erlang would not be portable to Javascript (or native code if they build a native compiler). I foresee multiple “standard” libraries for different targets. I hope this is the case, as transpiled systems often try to ship entire runtimes on the target platform instead of treating them as native first-class citizens.

We’re currently targeting erlang, so, we’ll have to add the gleam_erlang library. This is easily done, and you can probably guess the command (hint: see italics).

gleam add gleam_erlang

If you open the gleam.toml file, you should see the new library listed under dependencies.

gleam_erlang is actually pretty bare-bones and seems like a rather random collection of primitives. This is indicative of a very young ecosystem more than anything. But it contains a get_line function, which is all we need (for now).

Change your helo_world.gleam file as follows:

import gleam/io
import gleam/string
import gleam/erlang

pub fn main() {
  assert Ok(name) = erlang.get_line("Enter your name: ")

  ["Hello, ", name |> string.trim, "! 🎉"]
  |> string.concat
  |> io.println
}

Again, we see a few new constructs. The most interesting is the assert keyword. This construct behaves differently from what I’ve seen in most languages. Syntactically, it acts like a suped-up let binding.

erlang.get_line returns a Result type, which is used to indicate a potential error condition. I don’t want to go into the nuances of pattern matching just yet (It’s no different from Rust if you are familiar with Rust), so suffice it to say that get_line can return either Ok(value) or Error(reason).

We could handle the Error condition using a case expression (and we will, but later). In this case, it’s really quite unlikely for get_line to error, and I wouldn’t know what to do with it if it did. Probably just print an error message.

This is where assert comes in. assert basically says, “if the function returns an Ok value, bind name to the value inside the Ok pattern. Otherwise, crash.”

It’s a concise way to crash the program if you don’t feel like handling stuff. It’s similar to unwrap in Rust.

get_line returns a string with a newline appended, so I piped the return value into string.trim before storing it in the list. The pipeline for concatenating and outputting the string hasn’t changed.

Try it! gleam run and enter your name at the prompt. Mine looks like this:

❯ gleam run
  Compiling hello_world
   Compiled in 0.34s
    Running hello_world.main
Enter your name: Dusty
Hello, Dusty! 🎉

If you want to test out the error condition, run it and press Ctrl+D at the prompt to insert and end of file character. Gleam won’t like that and you’ll get a <<"Assertion pattern match failed">> error.

On Finding Dependencies

I’d like to digress a bit on the noble art of finding dependency libraries for Gleam. It’s not that well documented (which is one of the reasons I’m writing this), yet. There are links to the language tour, and standard library but not to other key packages in the ecosystem.

It actually took some dedicated searching to find the get_line function, among others. So here are a couple tips:

  • Awesome Gleam contains a list of gleam libraries that may be of value to you.
  • I’ve noticed that projects rarely link to their own documentation. For example, Awesome Gleam links to the gleam-erlang repository, but there isn’t much in the way of documentation there.
  • hexdocs allows you to search for documentation of various projects. hex is an Erlang/Elixer package manager, and most of the gleam libraries use it as well. Search for gleam to find several packages (many of which are not listed on Awesome Gleam) and to find documentation for packages you know about.

Pattern Matching with Case Expressions

Like Rust, Rescript, and most functional languages, Gleam programs make extensive use of pattern matching. The principle construct is the case expression.

“Pattern matching” means that the decision as to which arm to execute depends on the shape of the data, rather than its value. The Result type we’ve already seen is an extremely common “alternative values” pattern. The assert statement and its sibling try help keep pattern matching on Result tidy when you don’t care about intermediate values, but when you want to explicitly handle the error condition, case is the way to do it.

Let’s check it out:

import gleam/io
import gleam/string
import gleam/erlang

pub fn main() {
  case erlang.get_line("Enter your name: ") {
    Error(erlang.Eof) -> io.println("\nEnd of File")
    Ok(name) ->
      [
        "Hello, ",
        name
        |> string.trim,
        "! 🎉",
      ]
      |> string.concat
      |> io.println
  }
}

The case keyword kicks off pattern matching and is immediately followed by the pattern being matched on. In this case, it’s the result of get_line.

The get_line documentation indicates that the error reason will be an instance of GetLineError which is either Eof or NoData (I’m ignoring NoData for now).

Curly braces indicate an expression block in Gleam. Inside the curly braces, we have two pattern “arms”, each denoted by a pattern, followed by a ->, and the expression to be executed if that pattern matches. At most one of these arms will be executed, depending on whether you type ctrl-D or a legitimate string at the prompt.

Most soundly typed languages let you know if your case structure is missing an arm, and I was disappointed to discover Gleam does not do so (at least, not yet). It is too easy to accidentally (or intentionally, in this case) overlook possible cases, as I did with NoData.

This missing feature means that errors that can be caught at compile-time are actually not found until runtime, meaning the user has to learn about it for you.

I don’t know how to cause NoData, so to demonstrate this, I changed erlang.Eof to erlang.NoData in the above program. That meant that erlang.Eof is not properly handled. Pressing ctrl-D at the prompt results in an inelegant runtime error:

❯ gleam run
  Compiling hello_world
   Compiled in 0.32s
    Running hello_world.main
Enter your name: exception error: no case clause matching {error,eof}
  in function  hello_world:main/0 (build/dev/erlang/hello_world/build/hello_world.erl, line 8)

Command Line Arguments

Let’s make another adjustment. Instead of querying the user on standard input, let’s give them the option to pass a value in using a command line argument.

import gleam/io
import gleam/string
import gleam/erlang
import gleam/list

pub fn main() {
  let arguments = erlang.start_arguments()

  assert Ok(name) =
    arguments
    |> list.at(0)

  ["Hello, ", name, "! 🎉"]
  |> string.concat
  |> io.println
}

Now you can run your code with something like gleam run Dusty you’ll see the welcome greeting printed to the screen. I quite like my name, but if you have a different one, then you should probbaly use that instead.

There’s a new import for the gleam/list module, which ships with the standard library and contains a bunch of useful functions for manipulating linked lists. Note that we’ve still got the gleam/erlang import even though we’ve stopped reading from stdin for now. That’s because of start_arguments.

The erlang.start_arguments() function returns the command line arguments that were passed into the program. Unlike some languages, it does not include the name of the program itself. The return value is a list of strings.

We use the list.at function to get the first element out of the list. Remember how assert works: We’re telling Gleam we want it to crash if the user doesn’t specify at least one argument. That’s not terribly friendly, but we’re not here to make friends, now, are we?

We’re also droppping any extra arguments the user may have supplied, meaning we’ll also alienate any friends who have multiple names. At least it won’t crash, but it will discard anything after the first argument.

Actually, that’s a lot of potentially unhappy people. Gleam advertises itself as a “friendly” language, so perhaps we should maintain that spirit.

Shape of a List

Remember how the case statement is designed to pattern match on the shape of its argument? If the argument is a list, the “shape” is the number of elements in the list. Let’s replace our assert with a case statement:


import gleam/io
import gleam/string
import gleam/erlang
import gleam/list

pub fn main() {
  let arguments = erlang.start_arguments()

  let name = case arguments {
    [] -> "Stranger"
    [name] -> name
    _names -> "Big named person"
  }

  ["Hello, ", name, "! 🎉"]
  |> string.concat
  |> io.println
}

Replacing assert with a case is probably a common activity. You prototype your code with assert, then once the happy bath is working, you switch it to case to fix the error conditions.

This time, we’ve replaced the assert with three possible arms. If the user has not supplied a name, we call them “Stranger”. If they give us exactly one argument, that’s what we call them. But if they give us many arguments, we just give them a stupid nickname. The “shape” of the list chooses which of those paths is taken.

Note: The _ in _name means “ignore this value, I’m not using it”.

Truth be told, neither nicknames nor the title “stranger” is particularly friendly, but our program is definitely warming up to folks a bit. Let’s see if we can draw it further out of its shell.

Concatenating with Spaces

Let’s tackle the nickname scenario first. Basically, if we are given a large list, we want to concatenate the arguments in the list. Your first attempt might be to replace the _names arm with the following:

    names ->
      names
      |> string.concat

I’ve removed the underscore now because we are actually using the variable.

However, this naive attempt makes our program look like it’s talking really fast:

❯ gleam run -- Dusty Phillips
  Compiling hello_world
   Compiled in 0.40s
    Running hello_world.main
Hello, DustyPhillips! 🎉

I went to all that trouble to type a space in the argument and my program took it upon itself to just remove it. How rude!

In Python, I’d write " ".join(names) which is an admittedly bizarre construct. Gleam has a join function in the string library, and with pipe-first syntax, it’s much more elegante: names |> string.join(" ")

import gleam/io
import gleam/string
import gleam/erlang
import gleam/list

pub fn main() {
  let arguments = erlang.start_arguments()

  let name = case arguments {
    [] -> "Stranger"
    [name] -> name
    names ->
      names
      |> string.join(" ")
  }

  ["Hello, ", name, "! 🎉"]
  |> string.concat
  |> io.println
}

Now if I run the program with a name my mother tended to call me when I was young, it reacts properly (even if I didn’t):

❯ gleam run -- Dustin Dont Bug The Dog When Its Eating         
  Compiling hello_world
   Compiled in 0.33s
    Running hello_world.main
Hello, Dustin Dont Bug The Dog When Its Eating! 🎉

Apropos of nothing, I have bite scars to prove I didn’t like that name. I’m told it was “my own damn fault”.

Interactive Fallback

Our program is pretty friendly now, if you consider calling someone “Stranger” to be friendly (some subcultures do, some don’t). It might be more polite to invite them to introduce themselves, though. To do that, we can nest our original get_line case statement within the new argument based one, prompting for a name only if they don’t give us one:

import gleam/io
import gleam/string
import gleam/erlang
import gleam/list

pub fn main() {
  let arguments = erlang.start_arguments()

  let name = case arguments {
    [] ->
      case erlang.get_line("Howdy Stranger, what is your name? ") {
        Ok(name) ->
          name
          |> string.trim
        Error(_) -> "Stranger"
      }
    [name] -> name
    names ->
      names
      |> list.intersperse(" ")
      |> string.concat
  }

  ["Hello, ", name, "! 🎉"]
  |> string.concat
  |> io.println
}

But that case looks rather messy.

Tidying Up

In addition to assert (and try, which I’m not going to cover today), the gleam.result module in the standard library has a bunch of nice functions to make working with results feel cleaner.

Results are typically the return value of any function that might fail, and in my experience, it is not uncommon to have deep chains of them. Thus it is very important that working with them be ergonomic for the author of code, and highly legible for the reader.

The “Give me a value if it’s Ok, otherwise use this default” case is a very common action to take when given a Result value. The gleam standard library gives us the result.unwrap function, which makes our code much easier on the eyes:

import gleam/io
import gleam/string
import gleam/erlang
import gleam/list

pub fn main() {
  let arguments = erlang.start_arguments()

  let name = case arguments {
    [] ->
      erlang.get_line("Howdy Stranger, what is your name? ")
      |> result.unwrap("Stranger")
    names ->
      names
      |> list.intersperse(" ")
      |> string.concat
  }

  ["Hello, ", name, "! 🎉"]
  |> string.concat
  |> io.println
}

I’ve also removed the single element name arm because the multi-element arm handled it just as well.

Summary

That’s all I’ve got for today. Gleam is a pretty language that I can tell I’m going to have a lot of fun with. It’s immature, but it is building on a solid foundation that I think will grow into a very ergonomic coding experience.

This article covered the “hello world” of Gleam development, and introduced some iconic functional paradigms including pipes and pattern matching. We encountered a couple of standard library functions in string and list, and also learned that some functionality that we might consider standard actually come from a separate library.

Most importantly, we developed a friendly program. I hope you enjoyed the writing and will return for the next instalment. Don’t be a stranger!