Yan Yi

TIL: Importing Constructors in Gleam

· Yan Yi Goh· 3 min read

Another Exercism exercise. This time round, I was working with the Option type, the equivalent of Maybe in Elm.

With the following piece of code:

import gleam/bool
import gleam/option.{type Option}

pub type Player {
  Player(name: Option(String), level: Int, health: Int, mana: Option(Int))
}

pub fn revive(player: Player) -> Option(Player) {
  case player.health {
    0 -> Some(Player(..player, health: 100))
      |> option.map(fn(p) {
        use <- bool.guard(when: p.level >= 10, return: Player(..p, mana: 100))
        p
      })
    _ -> None
  }
}

I was getting the following compilation error:

error: Unknown variable
   ┌─ /src/main.gleam:10:10
100 -> Some(Player(..player, health: 100))
   │          ^^^^

The custom type variant constructor `Some` is not in scope here.

error: Unknown type for record access
   ┌─ /src/main.gleam:12:33
12 │         use <- bool.guard(when: p.level >= 10, return: Player(..p, mana: 100))
   │                                 ^ I don't know what type this is

In order to access a record field we need to know what type it is, but I
can't tell the type here. Try adding type annotations to your function and
try again.

error: Type mismatch
   ┌─ /src/main.gleam:12:68
12 │         use <- bool.guard(when: p.level >= 10, return: Player(..p, mana: 100))
   │                                                                    ^^^^^^^^^

Expected type:

    Option(Int)

Found type:

    Int

error: Unknown variable
   ┌─ /src/main.gleam:15:10
15 │     _ -> None
   │          ^^^^

The custom type variant constructor `None` is not in scope here.

What happened was that, even if I imported Option in the second line, I did not import the constructors:

-import gleam/option.{type Option}
+import gleam/option.{type Option, Some, None}

Then, afterward, I will need to fix the mana to use Some:

-        use <- bool.guard(when: p.level >= 10, return: Player(..p, mana: 100))
+        use <- bool.guard(when: p.level >= 10, return: Player(..p, mana: Some(100)))

Re-reading Type imports, I quote the following:

Unlike functions, Gleam types are commonly imported in an unqualified way.

Since the Some and None are constructor functions of the Option type, I needed to import it since these are not native types of the language.

As for the Result type, it seems to be native type. Therefore, I did not need to import Ok or Error when working with the Result type.


I refactored my solution for the revive function to something like:

pub fn revive(player: Player) -> Option(Player) {
  case player.health {
    0 -> Some(Player(..player, health: 100))
      |> option.map(revive_with_full_mana)
    _ -> None
  }
}

fn revive_with_full_mana(player: Player) -> Player {
  use <- bool.guard(when: player.level < 10, return: player)
  Player(..player, mana: Some(100))
}

At least if we needed to write tests for revive_with_full_mana, we can do so instead of chucking it into an inline function in the original implementation. Plus, early return concept from Go, too.

I am still not very sure if the use <- bool.guard will be readable over time as compared to case statements (more verbose, more lines of code).