Gleam a lovely little language

When was the last time you programmed without using an if-statement? Ok, what about a for-loop? Recently I have been digging into the Gleam programming language. A small functional language that, as you probably guessed, neither has if-statements nor for-loops. And it has been a lovely experience.

A few months ago I stumbled upon this language called Gleam. I have been playing around with it since and I have really been enjoying myself, it is a lovely little language!

1. I have reached that age where it hurts less to get up earlier than to stay up too late. Also my kids are teenagers, so getting up a few hours before them is really not that hard. In fact, I have been enjoying it so much that I have gone up early1 during the weekends to get some quality coding time before the rest of my family wakes up.

So what is it that makes this language so pleasant to work with? I think it is a combination of things, and of course, a personal preference. Before we dive in let's see what some Gleam code looks2 like!

/// Try to identify the content-type for the given path.
/// Defaults to application/octet-stream.
pub fn identify_content_type(path: String) -> String {
  let seq =
    path
    |> string.split(on: ".")
    |> list.reverse()
    |> list.map(string.trim)

  case seq {
    ["html", ..] -> "text/html"
    ["css", ..] -> "text/css"
    ["js", ..] -> "text/javascript"
    _ -> "application/octet-stream"
  }
}

2. Mind you that I am not a Gleam expert! This piece of code might not be idiomatic, or the most efficient way of doing this. But I think it serves as a good example of Gleams capabilities and syntax.

I am not going to step through that example just yet! It was an appetizer. If you think that the code looks awful feel free to close this tab and move on. The Internet might have something else for you up the road.

My road to Gleam

What drew me to Gleam in the first place was actually that it is an alternative to Elm! I have been lurking around in the Elm community for years, and I really like The Elm Architecture 3, the philosophy behind the language, etc. But, Elm is an ML-family language and that syntax never really clicked with me. 3. I think that Evan and the Elm community made a real dent in the programming world. The Elm architecture, the friendly compiler errors, the focus on correctness, etc. I highly recommend you to have a look at it.

Scala is another language I like (while influenced by ML, I find that the Scala syntax is more to my liking4).

One of Scala's strengths is that it allows you to mix functional programming with object-oriented programming. I think both types of programming have their place, and I use both in my day-to-day work. But being raised in OOP, I tend to switch to using that to solve a problem when FP does not come naturally.

In Gleam, there is no OOP so I cannot do that. And the language shares some of the features I enjoy in Scala, like pattern-matching, expressions, result types, etc. 4. I know that some people argue that syntax is superficial and that the semantics is what matters. To me, both matter a great deal.

The thing that pushed me to open an editor and start using it was Lustre. An Elm-inspired web framework for Gleam.

Gleam itself

As I mentioned, Gleam is a small language. It has few reserved keywords, a concise syntax and only a small number of language features.

Gleam compiles to Erlang5, and is run on the BEAM virtual machine. Gleam also compiles to JavaScript which then can run either in the browser or using a JavaScript runtime such as Node or Bun. 5. Funny thing regarding being superficial and syntax. I just cannot stand the Erlang syntax! I have been interested in Erlang and BEAM for a long time, but the syntax has always kept me away.

Here is my way of describing Gleam:

  • Functional language, immutable data structures and mostly pure functions.
  • Type-safe, types, exhaustive checks, no nulls or exceptions.
  • Concurrent, built on top of BEAM and OTP (Erlang's concurrency library).
  • Simple, small grammar, no type classes, etc.
  • Fun, having fun with functions!

Two things that initially stand out when you start working with Gleam are the lack of if statements, and the absence of loops.

What, there is no if?

No, it has something much better, expressions and pattern-matching!

let result = case number {
  200 -> "OK
  404 -> "Not Found"
  _ -> "Not Implemented"
}

Ok, maybe I stretched the truth. There is actually a if keyword6 in Gleam. It is part of something called case expressions, which is used to do conditional branching. 6. It is not a statement though, so I hope you can forgive me.

let number: Int = 33

let result = case number {
  x if x > 10 -> "large"
  _ -> "small"
}

io.println("The number is " <> result)

Here the case expression will match the value of number against the patterns in each branch. The first branch binds the value of number to x which is used to evaluate the expression > 10 (also known as a guard expression).

The second branch uses the wildcard pattern _ to match any value, in this case we know that the value must be less than 10 as there are no other branches that match.

As the entire case is an expression, the result is assigned to the variable result, which we later use to print7 out the result. 7. <> is the string concatenation operator in Gleam.

The case expression is how you do conditional branching in Gleam, it is both powerful and safe. The compiler forces you to cover all possible cases, and warns you if there is any unreachable code.

In the first example, we used the case expression to match against a list of strings:

case seq {
  ["html", ..] -> "text/html"
  ["css", ..] -> "text/css"
  ["js", ..] -> "text/javascript"
  _ -> "application/octet-stream"
}

Here the variable seq is a list of strings, let's say that the value of seq is let seq = ["js", "example/filename"]. That will match the third branch, as it matches any list that has "js" as the first element and any number of other elements (the .. syntax, similar to what is sometimes called tail in other languages).

Matching lists can be more advanced as well:

let result = case lst {
  [] -> "List is empty"
  [_] -> "List have exactly one item"
  [_, _] -> "List has exactly two items"
  [_, _, ..] -> "List has at least three items"
}

Only using case expressions takes some getting used to. I have commonly found myself in situations where I want to perform something based on a condition. E.g., add something to a list if a condition is met. What I have been doing in those situations is to use a representation of none. Like an empty list.

For example, in one of my hobby applications I have a menu that might have an extra line of information called an appendix.

fn dropdown_item(text: String, appendix: Option(String)) {
  // Create the appendix element, or no element if no appendix is given.
  let extra = case appendix {
    option.Some(t) -> html.div([], [html.text(t)])
    option.None -> element.none()
  }

  html.div([], [
    html.div([], [html.text(text)]),
    extra,
  ])
}

Don't worry too much about the HTML stuff, that is just8 Lustre's way of producing HTML. 8. "just" is not really fair, working with HTLM as code rather than a template is very powerful and is now my preferred way. I hope to describe how well this composes in a future article.

Here we either create a DIV element with one or two child elements. The first is a DIV containing The text argument, and the other is either another DIV with the appendix text, or no element.

While a few more lines of code, I find this code to more clearly communicate what is happening.

Loops, what loops?

That's right, there are no loops in Gleam! The list module contains the usual fold, map, and an each function. Those are your options, so to sum the values in a list, you can fold over it:

fn sum(lst: List(Int)) -> Int {
  list.fold(lst, 0, fn(acc, v) { acc + v })
}

If you want to calculate the factorial9 value of an integer, you have to use recursion. In languages like JavaScript or Python you could have done this either using recursion or by using a for-loop. In Gleam, there is only recursion. 9. I have never needed this in my 22 years of professional programming. But it seems to be the golden illustration used for recursion, so I do not dare to do it any differently.

pub fn factorial(x: Int) -> Int {
  case x {
    0 -> 1
    1 -> 1
    _ -> x * factorial(x - 1)
  }
}

If you are unfamiliar with recursion (I am a bit rusty myself, due to mainly programming in C# at work), the source code of the Gleam standard library is straightforward to read and understand. They often have an internal function named with a _loop suffix that hides the accumulator. E.g. list.take

Types and records

You have built-in types, custom types and records in Gleam. They all share the same construct and are quite straightforward.

You can alias a type like this:

type Number =
  Int

A type can have different variants:

// Season is a type.
pub type Season {
  // Spring is a variant of that type.
  Spring
  // Summer is another variant of the same type.  
  Summer
  Autumn
  Winter
}

A type can be a record, carrying data:

// A record is a type that can hold data.
pub type User {
  // Convention is to use the same name for type and variant when there is only one.
  User(name: String)
}

There is more to the language than what I have covered so far, but this is the gist of it, IMHO. The Gleam Language Tour illustrates the language in more detail using consumable pieces of example code.

World class toolchain!

One thing that really stands out is the Gleam toolchain. It is excellent, and I know that the Gleam community is working hard to improve it on every release!

  • The compiler is fast.
  • The compiler errors are quite specific and helpful.
  • Dependency management is built-in.
  • Testing is built-in.
  • Formatting of code is built-in.
  • Editor integration is great.

I have been using Zed as my editor for all my Gleam coding, and everything just works. I get inline errors, code completion, goto definition, etc. out of the box. I have not made a single configuration.

When you scaffold a new project with gleam new, you even get your first unit-test set up. I like that.

The language server is also helpful, making you aware of better ways to write your code. Yesterday I wrote a piece of code that was matching a HTTP request. First I started with a match expression using a temporary tuple to match on two values. I got a yellow swiggly line that informed me that the match expression has support for multiple subjects. This is something I expect in Rider when programming in C#, but not in a young language like Gleam. Impressive!

I even set up an application with a separate backend (Mist HTTP server) and frontend (Lustre web framework) that used a shared library. It was a breeze. I added a new dependency to the shared project using a relative path and set the compilation target to 'javascript' for the frontend, done.

I have made a few comparisons with Scala. Here Gleam is light years ahead of both SBT and Metals. In fact, I think Gleam is best in class, all languages considered. Kudos to the Gleam team!

Friendly Community

I have not interacted with the Gleam community that much yet. I am an introvert that tends to observe rather than participate at first. However, my impression from my side of the road is that the community is helpful and friendly. I have not observed any elitist Monad-warriors seen in other communities.10 10. Parts of the Scala community suffer from this unfortunately (although it has become better!)..

  • There is a quite active Discord where I have been reading the "questions" and "sharing" topics.
  • There is also the subreddit /r/gleamlang which is fairly quiet.

So, do I think Gleam is a good language? Absolutely! Do I think it is good enough for production usage? Maybe, maybe not. It is still a young language, and the ecosystem does not compare to the major languages yet. I am using it for my personal projects and as I wrote, the tooling has been rock solid so far.

I am very excited about Gleam, and I recommend you to try it out. Even if you, like me, only use it for your hobby projects. Putting yourself in a situation where you cannot use if-statements or for-loops is a great way to grow your skills as a programmer.

Do you have any thoughts on Gleam? Or any resources to recommend, please reach out!

Thank you for reading!