Basics
Here is a tiny program that prints out the text “Hello, Joe!”. We’ll explain how it works shortly.
In a normal Gleam project this program would be run using the command gleam run
on the command line, but here in this tour the program is compiled and run inside your web browser, allowing you to try Gleam without installing anything on your computer.
Try changing the text being printed to Hello, Mike!
and see what happens.
import gleam/io
pub fn main() {
io.println("Hello, Joe!")
}
</>Run code snippet
Gleam code is organized into units called modules. A module is a bunch of definitions (of types, functions, etc.) that seem to belong together. For example, the [gleam/io ](https:…
Basics
Here is a tiny program that prints out the text “Hello, Joe!”. We’ll explain how it works shortly.
In a normal Gleam project this program would be run using the command gleam run
on the command line, but here in this tour the program is compiled and run inside your web browser, allowing you to try Gleam without installing anything on your computer.
Try changing the text being printed to Hello, Mike!
and see what happens.
import gleam/io
pub fn main() {
io.println("Hello, Joe!")
}
</>Run code snippet
Gleam code is organized into units called modules. A module is a bunch of definitions (of types, functions, etc.) that seem to belong together. For example, the gleam/io module contains a variety of functions for printing, like println .
All gleam code is in some module or other, whose name comes from the name of the file it’s in. For example, gleam/io is in a file called io.gleam
in a directory called gleam
.
For code in one module to access code in another module, we import it using the import
keyword, and the name used to refer to it is the last part of the module name. For example, the gleam/io module is referred to as io
once imported.
The as
keyword can be used to refer to a module by a different name. See how the gleam/string module is referred to as text
here.
Comments in Gleam start with //
and continue to the end of the line. Comments go on the line before the item they are about, not after.
import gleam/io
import gleam/string as text
pub fn main() {
// Use a function from the `gleam/io` module
io.println("Hello, Mike!")
// Use a function from the `gleam/string` module
io.println(text.reverse("Hello, Joe!"))
}
</>Run code snippet
Normally functions from other modules are used in a qualified fashion, meaning the name used to refer the module goes before function name with a dot between them. For example, io.println(“Hello!”) .
It is also possible to specify a list of functions to import from a module in an unqualified fashion, meaning the function name can be used without the module qualifier (the name and the dot) before it.
Generally it is best to use qualified imports, as this makes it clear where the function is defined, making the code easier to read.
// Import the module and one of its functions
import gleam/io.{println}
pub fn main() {
// Use the function in a qualified fashion
io.println("This is qualified")
// Or an unqualified fashion
println("This is unqualified")
}
</>Run code snippet
Gleam has a robust static type system that helps you as you write and edit code, catching mistakes and showing you where to make changes.
Uncomment the line io.println(4) and see how a compile time error is reported as the io.println function only works with strings, not ints.
If you need to debug print something you can use the echo
keyword instead, as it will print a value of any type.
Gleam has no null
, no implicit conversions, no exceptions, and always performs full type checking. If the code compiles you can be reasonably confident it does not have any inconsistencies that may cause bugs or crashes.
import gleam/io
pub fn main() {
io.println("My lucky number is:")
// io.println(4)
// 👆️ Uncomment this line to see the error
// echo 4
// 👆️ You can use `echo` to debug print a value of any type!
}
</>Run code snippet
Gleam’s Int
type represents whole numbers.
There are arithmetic and comparison operators for ints, as well as the equality operator which works on all types.
When running on the Erlang virtual machine ints have no maximum and minimum size. When running on JavaScript runtimes ints are represented using JavaScript’s 64 bit floating point numbers.
The gleam/int standard library module contains functions for working with ints.
import gleam/int
pub fn main() {
// Int arithmetic
echo 1 + 1
echo 5 - 1
echo 5 / 2
echo 3 * 3
echo 5 % 2
// Int comparisons
echo 3 > 1 + 1
echo 2 < 1 - 1
echo 8 >= 1 + 3
echo 8 <= 5 - 3
// Equality works for any type
echo 2 == 1 + 1
echo 2 == 1 - 1
// Standard library int functions
echo int.max(42, 77)
echo int.clamp(5, 10, 20)
}
</>Run code snippet
Gleam’s Float
type represents numbers that are not integers.
Gleam’s numerical operators are not overloaded, so there are dedicated operators for working with floats.
Floats are represented as 64 bit floating point numbers on both the Erlang and JavaScript runtimes. The floating point behaviour is native to their respective runtimes, so their exact behaviour will be slightly different on the two runtimes.
Under the JavaScript runtime, exceeding the maximum (or minimum) representable value for a floating point value will result in Infinity
(or -Infinity
). Should you try to divide two infinities you will get NaN
as a result.
When running on the BEAM any overflow will raise an error. So there is no NaN
or Infinity
float value in the Erlang runtime.
Division by zero will not overflow, but is instead defined to be zero.
The gleam/float standard library module contains functions for working with floats.
import gleam/float
pub fn main() {
// Float arithmetic
echo 1.0 +. 1.5
echo 5.0 -. 1.5
echo 5.0 /. 2.5
echo 3.0 *. 3.5
// Float comparisons
echo 2.2 >. 1.3
echo 2.2 <. 1.3
echo 2.2 >=. 1.3
echo 2.2 <=. 1.3
// Equality works for any type
echo 3.0 == 1.5 *. 2.0
echo 2.1 == 1.2 +. 1.0
// Division by zero is not an error
echo 3.14 /. 0.0
// Standard library float functions
echo float.max(2.0, 9.5)
echo float.ceiling(5.4)
}
</>Run code snippet
Underscores can be added to numbers for clarity. For example, 1000000
can be tricky to read quickly, while 1_000_000
can be easier.
Ints can be written in binary, octal, or hexadecimal formats using the 0b
, 0o
, and 0x
prefixes respectively.
Floats can be written in a scientific notation.
pub fn main() {
// Underscores
echo 1_000_000
echo 10_000.01
// Binary, octal, and hex Int literals
echo 0b00001111
echo 0o17
echo 0xF
// Scientific notation Float literals
echo 7.0e7
echo 3.0e-4
}
</>Run code snippet
Gleam has the ==
and !=
operators for checking equality.
The operators can be used with values of any type, but both sides of the operator must be of the same type.
Equality is checked structurally, meaning that two values are equal if they have the same structure rather than if they are at the same memory location.
pub fn main() {
echo 100 == 50 + 50
echo 1.5 != 0.1 *. 10.0
}
</>Run code snippet
In Gleam strings are written as text surrounded by double quotes, and can span multiple lines and contain unicode characters.
The <>
operator can be used to concatenate strings.
Several escape sequences are supported:
\"
- double quote\\
- backslash\f
- form feed\n
- newline\r
- carriage return\t
- tab\u{xxxxxx}
- unicode codepoint
The gleam/string standard library module contains functions for working with strings.
import gleam/io
import gleam/string
pub fn main() {
// String literals
io.println("👩💻 こんにちは Gleam 🏳️🌈")
io.println(
"multi
line
string",
)
io.println("\u{1F600}")
// Double quote can be escaped
io.println("\"X\" marks the spot")
// String concatenation
io.println("One " <> "Two")
// String functions
io.println(string.reverse("1 2 3 4 5"))
io.println(string.append("abc", "def"))
}
</>Run code snippet
A Bool
is either True
or False
.
The ||
, &&
, and !
operators can be used to manipulate bools.
The ||
and &&
operators are short-circuiting, meaning that if the left hand side of the operator is True
for ||
or False
for &&
then the right hand side of the operator will not be evaluated.
The gleam/bool standard library module contains functions for working with bools.
import gleam/bool
pub fn main() {
// Bool operators
echo True && False
echo True && True
echo False || False
echo False || True
// Bool functions
echo bool.to_string(True)
}
</>Run code snippet
A value can be assigned to a variable using let
.
Variable names can be reused by later let bindings, but the values they reference are immutable, so the values themselves are not changed or mutated in any way.
In Gleam variable and function names are written in snake_case
.
import gleam/io
pub fn main() {
let x = "Original"
io.println(x)
// Assign `y` to the value of `x`
let y = x
io.println(y)
// Assign `x` to a new value
let x = "New"
io.println(x)
// The `y` still refers to the original value
io.println(y)
}
</>Run code snippet
If a variable is assigned but not used then Gleam will emit a warning.
If a variable is intended to not be used, then the name can be prefixed with an underscore, silencing the warning.
Try changing the variable name to score
to see the warning.
pub fn main() {
// This variable is never used
let _score = 1000
}
</>Run code snippet
Let assignments can be written with a type annotation after the name.
Type annotations may be useful for documentation purposes, but they do not change how Gleam type checks the code beyond ensuring that the annotation is correct.
Typically Gleam code will not have type annotations for assignments.
Try changing a type annotation to something incorrect to see the compile error.
pub fn main() {
let _name: String = "Gleam"
let _is_cool: Bool = True
let _version: Int = 1
}
</>Run code snippet
Other modules may also define types that we wish to refer to. In this case we need to import them.
Like functions, types can be referred to in a qualified way by putting the imported module name and a dot before the type name. For example, bytes_tree.BytesTree
Types can also be imported in an unqualified way by listing them in the import statement with the word type
before the type name.
Unlike functions, Gleam types are commonly imported in an unqualified way.
import gleam/bytes_tree
import gleam/string_tree.{type StringTree}
pub fn main() {
// Referring to a type in a qualified way
let _bytes: bytes_tree.BytesTree = bytes_tree.new()
// Refering to a type in an unqualified way
let _text: StringTree = string_tree.new()
}
</>Run code snippet
A type alias can be used to refer to a type by a different name. Giving a type an alias doesn’t make a new type, it is still the same type.
A type’s name always starts with a capital letter, contrasting to variables and functions, which start with a lowercase letter.
When the pub
keyword is used the type alias is public and can be referred to by other modules.
Type aliases should be used rarely. Aliasing a type makes it less clear what it is, and does not provide the type safety benefits that using a custom type would give.
pub type Number =
Int
pub fn main() {
let one: Number = 1
let two: Int = 2
// Number and Int are two names for the same type
echo one == two
}
</>Run code snippet
Blocks are one or more expressions grouped together with curly braces. Each expression is evaluated in order and the value of the last expression is returned.
Any variables assigned within the block can only be used within the block.
Try uncommenting echo degrees
to see the compile error from trying to use a variable that is not in scope.
Blocks can also be used to change the order of evaluation of binary operators expressions.
*
binds more tightly than +
so the expression 1 + 2 * 3
evaluates to 7. If the 1 + 2
should be evaluated first to make the expression evaluate to 9 then the expression can be wrapped in a block: { 1 + 2 } * 3
. This is similar to grouping with parentheses in some other languages.
pub fn main() {
let fahrenheit = {
let degrees = 64
degrees
}
// echo degrees
// ^^^^^^^ This will not compile
// Changing order of evaluation
let celsius = { fahrenheit - 32 } * 5 / 9
echo celsius
}
</>Run code snippet
Lists are ordered collections of values.
List is a generic type, having a type parameter for the type of values it contains. A list of ints has the type List(Int)
, and a list of strings has the type List(String)
.
Lists are immutable single-linked lists, meaning they are very efficient to add and remove elements from the front of the list.
Counting the length of a list or getting elements from other positions in the list is expensive and rarely done. It is rare to write algorithms that index into sequences in Gleam, but when they are written a list is not the right choice of data structure.
pub fn main() {
let ints = [1, 2, 3]
echo ints
// Immutably prepend
echo [-1, 0, ..ints]
// Uncomment this to see the error
// echo ["zero", ..ints]
// The original lists are unchanged
echo ints
}
</>Run code snippet
As well as let assignments Gleam also has constants, which are defined at the top level of a module.
Constants must be literal values, functions cannot be used in their definitions.
Constants may be useful for values that are used throughout your program, permitting them to be named and to ensure there are no differences in the definition between each use.
Using a constant may be more efficient than creating the same value in multiple functions, though the exact performance characteristics will depend on the runtime and whether compiling to Erlang or JavaScript.
const ints: List(Int) = [1, 2, 3]
const floats = [1.1, 2.2, 3.3]
pub fn main() {
echo ints
echo ints == [1, 2, 3]
echo floats
echo floats == [1.1, 2.2, 3.3]
}
</>Run code snippet
Functions
The fn
keyword is used to define new functions.
Similar to blocks, each expression in the function body is evaluated in order and the value of the last expression is returned from the function. Gleam is an expression based language so there is no return
operator, but there are ways to conditionally return early from a function, which will be introduced in a later section.
The double
and multiply
functions are defined without the pub
keyword. This makes them private functions, they can only be used within this module. If another module attempted to use them it would result in a compiler error.
Like with assignments, type annotations are optional for function arguments and return values. It is considered good practice to use type annotations for functions, for clarity and to encourage intentional and thoughtful design.
pub fn main() {
echo double(10)
}
fn double(a: Int) -> Int {
multiply(a, 2)
}
fn multiply(a: Int, b: Int) -> Int {
a * b
}
</>Run code snippet
In Gleam functions are values. They can be assigned to variables, passed to other functions, and anything else you can do with values.
Here the function add_one
is being passed as an argument to the twice
function.
Notice the fn
keyword is also used to describe the type of the function that twice
takes as its second argument.
pub fn main() {
// Call a function with another function
echo twice(1, add_one)
// Functions can be assigned to variables
let my_function = add_one
echo my_function(100)
}
fn twice(argument: Int, passed_function: fn(Int) -> Int) -> Int {
passed_function(passed_function(argument))
}
fn add_one(argument: Int) -> Int {
argument + 1
}
</>Run code snippet
As well as module-level named functions, Gleam has anonymous function literals, written with the fn() { ... }
syntax.
Anonymous functions can be used interchangeably with named functions.
Anonymous functions can reference variables that were in scope when they were defined, making them closures.
pub fn main() {
// Assign an anonymous function to a variable
let add_one = fn(a) { a + 1 }
echo twice(1, add_one)
// Pass an anonymous function as an argument
echo twice(1, fn(a) { a * 2 })
let secret_number = 42
// This anonymous function always returns 42
let secret = fn() { secret_number }
echo secret()
}
fn twice(argument: Int, my_function: fn(Int) -> Int) -> Int {
my_function(my_function(argument))
}
</>Run code snippet
Gleam has a shorthand syntax for creating anonymous functions that take one argument and immediately call another function with that argument: the function capture syntax.
The anonymous function fn(a) { some_function(..., a, ...) }
can be written as some_function(..., _, ...)
, with any number of other arguments passed directly to the inner function. The underscore _
is a placeholder for the argument, equivalent to a
in fn(a) { some_function(..., a, ...) }
.
pub fn main() {
// These two statements are equivalent
let add_one_v1 = fn(x) { add(1, x) }
let add_one_v2 = add(1, _)
echo add_one_v1(10)
echo add_one_v2(10)
}
fn add(a: Int, b: Int) -> Int {
a + b
}
</>Run code snippet
Up until now each function has accepted precisely one type for each of its arguments.
The twice
function in the previous lesson on higher order functions only worked with functions that would take and return ints. This is overly restrictive, it should be possible to use this function with any type, so long as the function and the initial value are compatible.
To enable this, Gleam supports generics, also known as parametric polymorphism.
This works by using a type variable instead of specifying a concrete type. It stands in for whatever specific type is being used when the function is called. These type variables are written with a lowercase name.
Type variables are not like an any
type, they get replaced with a specific type each time the function is called. Try uncommenting twice(10, exclaim)
to see the compiler error from trying to use a type variable as an int and a string at the same time.
pub fn main() {
let add_one = fn(x) { x + 1 }
let exclaim = fn(x) { x <> "!" }
// Invalid, Int and String are not the same type
// twice(10, exclaim)
// Here the type variable is replaced by the type Int
echo twice(10, add_one)
// Here the type variable is replaced by the type String
echo twice("Hello", exclaim)
}
// The name `value` refers to the same type multiple times
fn twice(argument: value, my_function: fn(value) -> value) -> value {
my_function(my_function(argument))
}
</>Run code snippet
It’s common to want to call a series of functions, passing the result of one to the next. With the regular function call syntax this can be a little difficult to read as you have to read the code from the inside out.
Gleam’s pipe operator |>
helps with this problem by allowing you to write code top-to-bottom.
The pipe operator takes the result of the expression on its left and passes it as an argument to the function on its right.
It will first check to see if the left-hand value could be used as the first argument to the call. For example, a |> b(1, 2)
would become b(a, 1, 2)
. If not, it falls back to calling the result of the right-hand side as a function, e.g., b(1, 2)(a)
Gleam code is typically written with the “subject” of the function as the first argument, to make it easier to pipe. If you wish to pipe to a different position then a function capture can be used to insert the argument to the desired position.
If you need to debug print a value in the middle of a pipeline you can use |> echo
to do it.
import gleam/io
import gleam/string
pub fn main() {
// Without the pipe operator
io.println(string.drop_start(string.drop_end("Hello, Joe!", 1), 7))
// With the pipe operator
"Hello, Mike!"
|> string.drop_end(1)
|> string.drop_start(7)
|> io.println
// Changing order with function capturing
"1"
|> string.append("2")
|> string.append("3", _)
|> io.println
}
</>Run code snippet
When functions take several arguments it can be difficult 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. These labels are written before the argument name in the function definition.
When labelled arguments are used the order of the arguments does not matter, but all unlabelled arguments must come before labelled arguments.
There is no performance cost to using labelled arguments, it does not allocate a dictionary or perform any other runtime work.
Labels are optional when calling a function, it is up to the programmer to decide what is clearest in their code.
pub fn main() {
// Without using labels
echo calculate(1, 2, 3)
// Using the labels
echo calculate(1, add: 2, multiply: 3)
// Using the labels in a different order
echo calculate(1, multiply: 3, add: 2)
}
fn calculate(value: Int, add addend: Int, multiply multiplier: Int) {
value * multiplier + addend
}
</>Run code snippet
When local variables have the same names as a function’s labelled arguments, the variable names can be omitted when calling the function. This is known as shorthand syntax for labels.
The shorthand syntax can also be used for record constructor arguments.
pub fn main() {
let quantity = 5.0
let unit_price = 10.0
let discount = 0.2
// Using the regular label syntax
echo calculate_total_cost(
quantity: quantity,
unit_price: unit_price,
discount: discount,
)
// Using the shorthand syntax
echo calculate_total_cost(quantity:, unit_price:, discount:)
}
fn calculate_total_cost(
quantity quantity: Float,
unit_price price: Float,
discount discount: Float,
) -> Float {
let subtotal = quantity *. price
let discount = subtotal *. discount
subtotal -. discount
}
</>Run code snippet
Functions and other definitions can be marked as deprecated using the @deprecated
attribute.
If a deprecated function is referenced the compiler will emit a warning, letting the programmer know they ought to update their code.
The deprecation attribute takes a message and this will be displayed to the user in the warning. In the message explain to the user the new approach or replacement function, or direct them to documentation on how to upgrade.
pub fn main() {
echo old_function()
echo new_function()
}
@deprecated("Use new_function instead")
fn old_function() {
Nil
}
fn new_function() {
Nil
}
</>Run code snippet
Flow control
The case expression is the most common kind of flow control in Gleam code. It is similar to switch
in some other languages, but more powerful than most.
It allows the programmer to say “if the data has this shape then run this code”, a process called pattern matching.
Gleam performs exhaustiveness checking to ensure that the patterns in a case expression cover all possible values. With this you can have confidence that your logic is up-to-date for the design of the data you are working with.
Try commenting out patterns or adding new redundant ones, and see what problems the compiler reports.
import gleam/int
pub fn main() {
let x = int.random(5)
echo x
let result = case x {
// Match specific values
0 -> "Zero"
1 -> "One"
// Match any other value
_ -> "Other"
}
echo result
}
</>Run code snippet
Patterns in case expressions can also assign variables.
When a variable name is used in a pattern the value that is matched against is assigned to that name, and can be used in the body of that clause.
import gleam/int
pub fn main() {
let result = case int.random(5) {
// Match specific values
0 -> "Zero"
1 -> "One"
// Match any other value and assign it to a variable
other -> "It is " <> int.to_string(other)
}
echo result
}
</>Run code snippet
When pattern matching on strings the <>
operator can be used to match on strings with a specific prefix.
The pattern "Hello, " <> name
matches any string that starts with "Hello, "
and assigns the rest of the string to the variable name
.
pub fn main() {
echo get_name("Hello, Joe")
echo get_name("Hello, Mike")
echo get_name("System still working?")
}
fn get_name(x: String) -> String {
case x {
"Hello, " <> name -> name
_ -> "Unknown"
}
}
</>Run code snippet
Lists and the values they contain can be pattern matched on in case expressions.
List patterns match on specific lengths of lists. The pattern []
matches an empty list, and the pattern [_]
matches a list with one element. They will not match on lists with other lengths.
The spread pattern ..
can be used to match the rest of the list. The pattern [1, ..]
matches any list that starts with 1
. The pattern [_, _, ..]
matches any list that has at least two elements.
import gleam/int
import gleam/list
pub fn main() {
let x = list.repeat(int.random(5), times: int.random(3))
echo x
let result = case x {
[] -> "Empty list"
[1] -> "List of just 1"
[4, ..] -> "List starting with 4"
[_, _] -> "List of 2 elements"
_ -> "Some other list"
}
echo result
}
</>Run code snippet
Gleam doesn’t have loops, instead iteration is done through recursion, that is through top-level functions calling themselves with different arguments.
A recursive function needs to have at least one base case and at least one recursive case. A base case returns a value without calling the function again. A recursive case calls the function again with different inputs, looping again.
The Gleam standard library has functions for various common looping patterns, some of which will be introduced in later lessons, however for more complex loops manual recursion is often the clearest way to write it.
Recursion can seem daunting or unclear at first if you are more familiar with languages that have special looping features, but stick with it! With time it’ll become just as familiar and comfortable as any other way of iterating.
pub fn main() {
echo factorial(5)
echo factorial(7)
}
// A recursive functions that calculates factorial
pub fn factorial(x: Int) -> Int {
case x {
// Base case
0 -> 1
1 -> 1
// Recursive case
_ -> x * factorial(x - 1)
}
}
</>Run code snippet
When a function is called a new stack frame is created in memory to store the arguments and local variables of the function. If lots of these frames are created during recursion then the program would use a large amount of memory, or even crash the program if some limit is hit.
To avoid this problem Gleam supports tail call optimisation, which allows the stack frame for the current function to be reused if a function call is the last thing the function does, removing the memory cost.
Unoptimised recursive functions can often be rewritten into tail call optimised functions by using an accumulator. An accumulator is a variable that is passed along in addition to the data, similar to a mutable variable in a language with while
loops.
Accumulators should be hidden away from the users of your code; they are internal implementation details. To do this write a public function that calls a recursive private function with the initial accumulator value.
pub fn main() {
echo factorial(5)
echo factorial(7)
}
pub fn factorial(x: Int) -> Int {
// The public function calls the private tail recursive function
factorial_loop(x, 1)
}
fn factorial_loop(x: Int, accumulator: Int) -> Int {
case x {
0 -> accumulator
1 -> accumulator
// The last thing this function does is call itself
// In the previous lesson the last thing it did was multiply two ints
_ -> factorial_loop(x - 1, accumulator * x)
}
}
</>Run code snippet
While it is more common to use functions in the gleam/list module to iterate across a list, at times you may prefer to work with the list directly.
The [first, ..rest]
pattern matches on a list with at least one element, assigning the first element to the variable first
and the rest of the list to the variable rest
. By using this pattern and a pattern for the empty list []
a function can run code on each element of a list until the end is reached.
This code sums a list by recursing over the list and adding each int to a total
argument, returning it when the end is reached.
pub fn main() {
let sum = sum_list([18, 56, 35, 85, 91], 0)
echo sum
}
fn sum_list(list: List(Int), total: Int) -> Int {
case list {
[first, ..rest] -> sum_list(rest, total + first)
[] -> total
}
}
</>Run code snippet
Sometimes it is useful to pattern match on multiple values at the same time in one case expression.
To do this, you can give multiple subjects and multiple patterns, separated by commas.
When matching on multiple subjects there must be the same number of patterns as there are subjects. Try removing one of the _,
sub-patterns to see the compile time error that is returned.
import gleam/int
pub fn main() {
let x = int.random(2)
let y = int.random(2)
echo x
echo y
let result = case x, y {
0, 0 -> "Both are zero"
0, _ -> "First is zero"
_, 0 -> "Second is zero"
_, _ -> "Neither are zero"
}
echo result
}
</>Run code snippet
Alternative patterns can be given for a case clause using the |
operator. If any of the patterns match then the clause matches.
If a pattern defines a variable then all of the alternative patterns for that clause must also define a variable with the same name and same type.
Currently it is not possible to have nested alternative patterns, so the pattern [1 | 2 | 3]
is not valid.
import gleam/int
pub fn main() {
let number = int.random(10)
echo number
let result = case number {
2 | 4 | 6 | 8 -> "This is an even number"
1 | 3 | 5 | 7 -> "This is an odd number"
_ -> "I'm not sure"
}
echo result
}
</>Run code snippet
The as
operator can be used to assign sub patterns to variables.
The pattern [_, ..] as first
will match any non-empty list and assign that list to the variable first
.
pub fn main() {
echo get_first_non_empty([[], [1, 2, 3], [4, 5]])
echo get_first_non_empty([[1, 2], [3, 4, 5], []])
echo get_first_non_empty([[], [], []])
}
fn get_first_non_empty(lists: List(List(t))) -> List(t) {
case lists {
[[_, ..] as first, ..] -> first
[_, ..rest] -> get_first_non_empty(rest)
[] -> []
}
}
</>Run code snippet
The if
keyword can be used with case expressions to add a guard to a pattern. A guard is an expression that must evaluate to True
for the pattern to match.
Guard expressions cannot contain function calls, case expressions, or blocks.
pub fn main() {
let numbers = [1, 2, 3, 4, 5]
echo get_first_larger(numbers, 3)
echo get_first_larger(numbers, 5)
}
fn get_first_larger(numbers: List(Int), limit: Int) -> Int {
case numbers {
[first, ..] if first > limit -> first
[_, ..rest] -> get_first_larger(rest, limit)
[] -> 0
}
}
</>Run code snippet
Data types
Lists are good for when we want a collection of one type, but sometimes we want to combine multiple values of different types. In this case tuples are a quick and convenient option.
The tuple access syntax can be used to get elements from a tuple without pattern matching. some_tuple.0
gets the first element, some_tuple.1
gets the second element, etc.
Tuples are generic types, they have type parameters for the types they contain. #(1, "Hi!")
has the type #(Int, String)
, and #(1.4, 10, 48)
has the type #(Float, Int, Int)
.
Tuples are most commonly used to return 2 or 3 values from a function. Often it is clearer to use a custom type where a tuple could be used. We will cover custom types next.
pub fn main() {
let triple = #(1, 2.2, "three")
echo triple
let #(a, _, _) = triple
echo a
echo triple.1
}
</>Run code snippet
Gleam has a few built in types such as Int
and String
, but custom types allow the creation of entirely new types.
A custom type is defined with the type
keyword followed by the name of the type and a constructor for each variant of the type. Both the type name and the names of the constructors start with uppercase letters.
Custom type variants can be pattern matched on using a case expression.
pub type Season {
Spring
Summer
Autumn
Winter
}
pub fn main() {
echo weather(Spring)
echo weather(Autumn)
}
fn weather(season: Season) -> String {
case season {
Spring -> "Mild"
Summer -> "Hot"
Autumn -> "Windy"
Winter -> "Cold"
}
}
</>Run code snippet
A variant of a custom type can hold other data within it. In this case the variant is called a record.
The fields of a record can be given labels, and like function argument labels they can be optionally used when calling the record constructor. Typically labels will be used for variants that define them.
It is common to have a custom type with one variant that holds data, this is the Gleam equivalent of a struct or object in other languages.
When defining custom types with one variant, the single variant is often named the same as the custom type, although it doesn’t have to be.
pub type Person {
Person(name: String, age: Int, needs_glasses: Bool)
}
pub fn main() {
let amy = Person("Amy", 26, True)
let jared = Person(name: "Jared", age: 31, needs_glasses: True)
let tom = Person("Tom", 28, needs_glasses: False)
let friends = [amy, jared, tom]
echo friends
}
</>Run code snippet
The record accessor syntax record.field_label
can be used to get contained values from a custom type record.
The accessor syntax can always be used for fields with the same name that are in the same position and have the same type for all variants of the custom type. Other fields can only be accessed when the compiler can tell which variant the value is, such after pattern matching in a case
expression.
The name
field is in the first position and has type String
for all variants, so it can be accessed.
The subject
field is absent on the Student
variant, so it cannot be used on all values of type SchoolPerson
. Uncomment the student.subject
line to see the compile error from trying to use this accessor.
pub type SchoolPerson {
Teacher(name: String, subject: String)
Student(name: String)
}
pub fn main() {
let teacher = Teacher("Mr Schofield", "Physics")
let student = Student("Koushiar")
echo teacher.name
echo student.name
// echo student.subject
}
</>Run code snippet
It is possible to pattern match on a record, this allows for the extraction of multiple field values from a record into distinct variables, similar to matching on a tuple or a list.
The let
keyword can only match on single variant custom types, or when the variant is known, such as after pattern matching with a case expression.
It is possible to use underscore _
or the spread syntax ..
to discard fields that are not required.
import gleam/io
pub type Fish {
Starfish(name: String, favourite_colour: String)
Jellyfish(name: String, jiggly: Bool)
}
pub type IceCream {
IceCream(flavour: String)
}
pub fn main() {
handle_fish(Starfish("Lucy", "Pink"))
handle_ice_cream(IceCream("strawberry"))
}
fn handle_fish(fish: Fish) {
case fish {
Starfish(_, favourite_colour) -> io.println(favourite_colour)
Jellyfish(name, ..) -> io.println(name)
}
}
fn handle_ice_cream(ice_cream: IceCream) {
// if the custom type has a single variant you can
// destructure it using `let` instead of a case expression!
let IceCream(flavour) = ice_cream
io.println(flavour)
}
</>Run code snippet
The record update syntax can be used to create a new record from an existing one of the same type, but with some fields changed.
Gleam is an immutable language, so using the record update syntax does not mutate or otherwise change the original record.
pub type SchoolPerson {
Teacher(name: String, subject: String, floor: Int, room: Int)
}
pub fn main() {
let teacher1 = Teacher(name: "Mr Dodd", subject: "ICT", floor: 2, room: 2)
// Use the update syntax
let teacher2 = Teacher(..teacher1, subject: "PE", room: 6)
echo teacher1
echo teacher2
}
</>Run code snippet
Like functions, custom types can also be generic, taking contained types as parameters.
Here a generic Option
type is defined, which is used to represent a value that is either present or absent. This type is quite useful! The gleam/option module defines it so you can use it in your Gleam projects.
pub type Option(inner) {
Some(inner)
None
}
// An option of string
pub const name: Option(String) = Some("Annah")
// An option of int
pub const level: Option(Int) = Some(10)
</>Run code snippet
Nil
is Gleam’s unit type. It is a value that is returned by functions that have nothing else to return, as all functions must return something.
Nil
is not a valid value of any other types. Therefore, values in Gleam are not nullable. If the type of a value is Nil
then it is the value Nil
. If it is some other type then the value is not Nil
.
Uncomment the line that assigns Nil
to a variable with an incompatible type annotation to see the compile time error it produces.
import gleam/io
pub fn main() {
let x = Nil
echo x
// let y: List(String) = Nil
let result = io.println("Hello!")
echo result == Nil
}
</>Run code snippet
Gleam doesn’t use exceptions, instead computations that can either succeed or fail return a value of the built-in Result(value, error)
type. It has two variants:
Ok
, which contains the return value of a successful computation.Error
, which contains the reason for a failed computation.
The type is generic with two type parameters, one for the success value and one for the error. With these the result can hold any type for success and failure.
Commonly a Gleam program or library will define a custom type with a variant for each possible problem that can arise, along with any error information that would be useful to the programmer.
This is advantageous over exceptions as you can immediately see what if any errors a function can return, and the compiler will ensure they are handled. No nasty surprises with unexpected exceptions!
A result value can be handled by pattern matching with a case
expression, but given how frequently results are returned this can become unwieldy. Gleam code commonly uses the gleam/result standard library module and use
expressions when working with results, both of which will be covered in later chapters.
import gleam/int
pub fn main() {
let _ = echo buy_pastry(10)
let _ = echo buy_pastry(8)
let _ = echo buy_pastry(5)
let _ = echo buy_pastry(3)
}
pub type PurchaseError {
NotEnoughMoney(required: Int)
NotLuckyEnough
}
fn buy_pastry(money: Int) -> Result(Int, PurchaseError) {
case money >= 5 {
True ->
case int.random(4) == 0 {
True -> Error(NotLuckyEnough)
False -> Ok(money - 5)
}
False -> Error(NotEnoughMoney(required: 5))
}
}
</>Run code snippet
Bit arrays represent a sequence of 1s and 0s, and are a convenient syntax for constructing and manipulating binary data.
Each segment of a bit array can be given options to specify the representation used for that segment.
size
: the size of the segment in bits.unit
: the number of bits that thesize
value is a multiple of.bits
: a nested bit array of any size.bytes
: a nested byte-aligned bit array.float
: a 64 bits floating point number.int
: an int with a default size of 8 bits.big
: big endian.little
: little endian.native
: the endianness of the processor.utf8
: utf8 encoded text.utf16
: utf16 encoded text.utf32
: utf32 encoded text.utf8_codepoint
: a utf8 codepoint.utf16_codepoint
: a utf16 codepoint.utf32_codepoint
: a utf32 codepoint.signed
: a signed number.unsigned
: an unsigned number.
Multiple options can be given to a segment by separating each with a dash: x:unsigned-little-size(2)
.
Bit arrays have limited support when compiling to JavaScript, not all options can be used. Full bit array support will be implemented in the future.
For more information on bit arrays see the Erlang bit syntax documentation.
pub fn main() {
// 8 bit int. In binary: 00000011
echo <<3>>
echo <<3>> == <<3:size(8)>>
// 16 bit int. In binary: 0001100000000011
echo <<6147:size(16)>>
// A bit array of UTF8 data
echo <<"Hello, Joe!":utf8>>
// Concatenation
let first = <<4>>
let second = <<2>>
echo <<first:bits, second:bits>>
}
</>Run code snippet
Standard library
The Gleam standard library is a regular Gleam package that has been published to the Hex package repository. You could opt to not use it if you wish, though almost all Gleam projects depend on it.
All of the modules imported so far in this guide, such as gleam/io , are from the standard library.
All of the documentation for the standard library is available on HexDocs. We will go over some of the most commonly used modules now.
import gleam/io
pub fn main() {
io.println("Hello, Joe!")
io.println("Hello, Mike!")
}
</>Run code snippet
The gleam/list standard library module contains functions for working with lists. A Gleam program will likely make heavy use of this module, the various functions serving as different types of loops over lists.
map makes a new list by running a function on each element in a list.
filter makes a new list containing only the elements for which a function returns true.
fold combines all the elements in a list into a single value by running a function left-to-right on each element, passing the result of the previous call to the next call.
find returns the first element in a list for which a function returns True
.
It’s worth getting familiar with all the functions in this module when writing Gleam code, you’ll be using them a lot!
import gleam/io
import gleam/list
pub fn main() {
let ints = [0, 1, 2, 3, 4, 5]
io.println("=== map ===")
echo list.map(ints, fn(x) { x * 2 })
io.println("=== filter ===")
echo list.filter(ints, fn(x) { x % 2 == 0 })
io.println("=== fold ===")
echo list.fold(ints, 0, fn(count, e) { count + e })
io.println("=== find ===")
let _ = echo list.find(ints, fn(x) { x > 3 })
echo list.find(ints, fn(x) { x > 13 })
}
</>Run code snippet
The gleam/result standard library module contains functions for working with results. Gleam programs will make heavy use of this module to avoid excessive nested case expressions when calling multiple functions that can fail.
map updates a value held within the Ok of a result by calling a given function on it. If the result is an error then the function is not called.
try runs a result-returning function on the value held within an Ok of a result. If the result is an error then the function is not called. This is useful for chaining together multiple function calls that can fail, one after the other, stopping at the first error.
unwrap extracts the success value from a result, or returning a default value if the result is an error.
Result functions are often used with pipelines to chain together multiple calls to result-returning functions.
import gleam/int
import gleam/io
import gleam/result
pub fn main() {
io.println("=== map ===")
let _ = echo result.map(Ok(1), fn(x) { x * 2 })
let _ = echo result.map(Error(1), fn(x) { x * 2 })
io.println("=== try ===")
let _ = echo result.try(Ok("1"), int.parse)
let _ = echo result.try(Ok("no"), int.parse)
let _ = echo result.try(Error(Nil), int.parse)
io.println("=== unwrap ===")
echo result.unwrap(Ok("1234"), "default")
echo result.unwrap(Error(Nil), "default")
io.println("=== pipeline ===")
int.parse("-1234")
|> result.map(int.absolute_value)
|> result.try(int.remainder(_, 42))
|> echo
}
</>Run code snippet
The gleam/dict standard library module defines Gleam’s Dict
type and functions for working with it. A dict is a collection of keys and values which other languages may call a hashmap or table.
new and from_list can be used to create new dicts.
insert and delete are used to add and remove items from a dict.
Like lists, dicts are immutable. Inserting or deleting an item from a dict will return a new dict with the item added or removed.
Dicts are unordered! If it appears that the items in a dict are in a certain order, it is incidental and should not be relied upon. Any ordering may change without warning in future versions or on different runtimes.
import gleam/dict
pub fn main() {
let scores = dict.from_list([#("Lucy", 13), #("Drew", 15)])
echo scores
let scores =
scores
|> dict.insert("Bushra", 16)
|> dict.insert("Darius", 14)
|> dict.delete("Drew")
echo scores
}
</>Run code snippet
Values in Gleam are not nullable, so the gleam/option standard library module defines Gleam’s Option type, which can be used to represent a value that is either present or absent.
The option type is very similar to the result type, but it does not have an error value. Some languages have functions that return an option when there is no extra error detail to give, but Gleam always uses result. This makes all fallible functions consistent and removes any boilerplate that would be required when mixing functions that use each type.
import gleam/option.{type Option, None, Some}
pub type Person {
Person(name: String, pet: Option(String))
}
pub fn main() {
let person_with_pet = Person("Al", Some("Nubi"))
let person_without_pet = Person("Maria", None)
echo person_with_pet
echo person_without_pet
}
</>Run code snippet
Advanced features
Opaque types are types where a custom type itself is public and can be used by other modules, but the constructors for the type are private and can only be used by the module that defines the type. This prevents other modules from constructing or