Fun and Functions with Gleam

Modules and Dependencies


Learning Objectives

  • You know how to split Gleam code into modules and files
  • You know how to import external libraries in Gleam

In Gleam, each project or package consists of an amount of modules. Each file within /src/ is its own module. The module names are restricted to lower case letters and underscores. Modules can be included with the import keyword.

External dependencies from the Gleam packages can be added with the gleam add command, or by editing the gleam.toml file manually. Additionally, the gleam.toml file can be used to configure other properties and metadata of the project.

Below is an example gleam.toml file:

name = "mepl_gleam"
version = "1.0.0"

description = "MEPL Gleam material"
# licences = ["Apache-2.0"]
# repository = { type = "github", user = "username", repo = "project" }
# links = [{ title = "Website", href = "https://gleam.run" }]
#
# For a full reference of all the available options, you can have a look at
# https://gleam.run/writing-gleam/gleam-toml/.

[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"

[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"

The complete schema and reference for the gleam.toml file can be found here.

Creating and importing modules

Accessing types, functions and bindings1 declared in other modules, requires importing them.

If, for instance, src/list.gleam has the following content:

pub type List(a) {
    Cons(a, List(a))
    Nil
}

pub fn map(f: fn(a) -> b, l: List(a)) -> List(b) {
    case l {
        Cons(h, t) -> Cons(f(h), map(f, t))
        Nil -> Nil
    }
}

And we’d want to use the List type and the associated function in another module, we’d first need to import it.

// src/main.gleam

import list.{type List, map}

fn add_one(l: List(Int)) -> List(Int) {
    map(fn(a) { a + 1 }, l)
}

Visibility

In the example above, the keyword pub is used to export the type List and function map. Exporting a type, function or binding allows them to be imported and used elsewhere. The general rule for visibility is that a public function or type cannot contain in its signature a private type. For instance, the following code would not compile:

// private
type Hello {}

// public
pub fn show_hello(h: Hello) -> String {
    "Hello, World"
}

The Gleam compiler gives us this helpful error when attempting to compile the code above:

error: Private type used in public interface
  ┌─ .../src/mepl.gleam:5:1

5 │ pub fn show_hello(h: Hello) -> String {
  │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The following type is private, but is being used by this public export.

    Hello

Private types can only be used within the module that defines them.

Including Gleam packages into your project

The Gleam language very young in terms of programming languages, which means that its ecosystem is also tiny. At the moment of writing, the Gleam package registry has a grand total of 774 packages (compared to, for example, the Rust package registry crates.io which has 166,423 packages).

As mentioned, adding a new package to the project is as simple as running gleam add with the package name. For example, if you need to work with times and dates, which are not implemented by the Gleam standard library, you might want to use a library such as birl.

Running gleam add birl will automatically figure out the latest version of the package and add the following entry to gleam.toml:

[dependencies]
birl = ">= 1.8.0 and < 2.0.0"

After that, we are able to use birl in our project:

import birl
import gleam/io

pub fn main() {
    let now = birl.now()
    io.println(birl.to_iso8601(now))
}
$ gleam run
2024-12-26T21:55:13.466+02:00
Loading Exercise...

Footnotes

  1. A binding is something that is declared with the let or const keywords