Language Tour
In this book we explore the fundamentals of the Gleam language, namely its syntax, core data structures, flow control features, and static type system. If you have some prior programming experience this will hopefully be enough to get you started with Gleam.
In some sections we touch on the runtime representation of various features. This is useful for programmers with Erlang or Elixir experience who wish to use Gleam alongside these languages. If you are using Gleam alone this information can be safely ignored.
Comments
Gleam allows you to write comments in your code.
Here’s a simple comment:
// Hello, world!
In Gleam, comments must start with two slashes and continue until the end of the
line. For comments that extend beyond a single line, you’ll need to include
//
on each line, like this:
// Hello, world! I have a lot to say, so much that it will take multiple
// lines of text. Therefore, I will start each line with // to denote it
// as part of a multi-line comment.
Comments can also be placed at the end of lines containing code:
pub fn add(x, y) {
x + y // here we are adding two values together
}
Comments may also be indented:
pub fn multiply(x, y) {
// here we are multiplying x by y
x * y
}
Strings
In Gleam Strings can be written as text surrounded by double quotes.
"Hello, Gleam!"
They can span multiple lines.
"Hello
Gleam!"
Under the hood Strings are UTF-8 encoded binaries and can contain any valid unicode.
"👩💻 こんにちは Gleam 💫"
Escape Sequences
Gleam supports common string escape sequences. Here's all of them:
Sequence | Result |
---|---|
\n | Newline |
\r | Carriage Return |
\t | Tab |
\" | Double Quote |
\\ | Backslash |
For example to include a double quote ("
) character in a string literal it
must be escaped by placing a backslash (\
) character before it.
"Here is a double quote -> \" <-"
Similarly all backslash characters must be escaped:
// A Windows filepath C:\Users\Gleam
"C:\\Users\\Gleam"
// A Decorative border /\/\/\/\
"/\\/\\/\\/\\"
Bool
A Bool can be either True
or False
.
Gleam defines a handful of operators that work with Bools.
False && False // => False
False && True // => False
True && False // => False
True && True // => True
False || False // => False
False || True // => True
True || False // => True
True || True // => True
&&
and ||
are short circuiting, meaning they don't evaluate the right
hand side if they don't have to.
&&
evaluates the right hand side if the left hand side is True
.
||
evaluates the right hand side if the left hand side is False
.
Gleam supports negation of Bools using either the !
operator or the
bool.negate
function from the gleam/bool
module.
!True // => False
!False // => True
Erlang interop
While written in the code using a capital letter, they are represented at
runtime with the atoms true
and false
, making them compatible with Elixir
and Erlang's booleans.
This is important if you want to use Gleam and Elixir or Erlang together in one project.
// Gleam
True
False
% Erlang
true.
false.
Int and Float
Gleam's main number types are Int and Float.
Ints
Ints are "whole" numbers.
Binary, octal, and hexadecimal ints begin with 0b
, 0o
, and 0x
respectively.
1
2
-3
4001
0b00001111
0o17
0xF
Gleam has several operators that work with Ints.
1 + 1 // => 2
5 - 1 // => 4
5 / 2 // => 2
3 * 3 // => 9
5 % 2 // => 1
2 > 1 // => True
2 < 1 // => False
2 >= 1 // => True
2 <= 1 // => False
Underscores can be added to Ints for clarity.
1_000_000 // One million
Floats
Floats are numbers that have a decimal point.
1.5
2.0
-0.1
Floats also have their own set of operators.
1.0 +. 1.4 // => 2.4
5.0 -. 1.5 // => 3.5
5.0 /. 2.0 // => 2.5
3.0 *. 3.1 // => 9.3
2.0 >. 1.0 // => True
2.0 <. 1.0 // => False
2.0 >=. 1.0 // => True
2.0 <=. 1.0 // => False
Underscores can also be added to Floats for clarity.
1_000_000.0 // One million
Let bindings
A value can be given a name using let
. Names can be reused by later let
bindings, but the values contained are immutable, meaning the values
themselves cannot be changed.
let x = 1
let y = x
let x = 2
x // => 2
y // => 1
Expression blocks
Every block in Gleam is an expression. All expressions in the block are executed, and the result of the last expression is returned.
let value: Bool = {
"Hello"
42 + 12
False
} // => False
Expression blocks can be used instead of parenthesis to change the precedence of operations.
let celsius = { fahrenheit - 32 } * 5 / 9
List
Lists are ordered collections of values. They're one of the most common data structures in Gleam.
Lists are homogeneous, meaning all the elements of a List must be of the same type. Attempting to construct a list of multiple types of element will result in the compiler presenting a type error.
[1, 2, 3, 4] // List(Int)
[1.22, 2.30] // List(Float)
[1.22, 3, 4] // Type error!
Prepending to a list is very fast, and is the preferred way to add new values.
[1, ..[2, 3]] // => [1, 2, 3]
Note that all data structures in Gleam are immutable so prepending to a list does not change the original list. Instead it efficiently creates a new list with the new additional element.
let x = [2, 3]
let y = [1, ..x]
x // => [2, 3]
y // => [1, 2, 3]
Tuples
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.
Gleam provides two ways to construct or match on tuples: the #(1, 2, 3)
format,
introduced in Gleam 0.15.0, and the original tuple(1, 2, 3)
format which will
be removed in a future version of Gleam.
#(10, "hello") // Type is #(Int, String)
#(1, 4.2, [0]) // Type is #(Int, Float, List(Int))
Once you have a tuple the values contained can be accessed using the .0
accessor syntax.
let my_tuple = #("one", "two")
let first = my_tuple.0 // "one"
let second = my_tuple.1 // "two"
Case
The case
expression is the most common kind of flow control in Gleam code. It
allows us to say "if the data has this shape then do that", which we call
pattern matching.
Here we match on an Int
and return a specific string for the values 0, 1,
and 2. The final pattern n
matches any other value that did not match any of
the previous patterns.
case some_number {
0 -> "Zero"
1 -> "One"
2 -> "Two"
n -> "Some other number" // This matches anything
}
Pattern matching on a Bool
value is the Gleam alternative to the if else
statement found in other languages.
case some_bool {
True -> "It's true!"
False -> "It's not true."
}
Gleam's case
is an expression, meaning it returns a value and can be used
anywhere we would use a value. For example, we can name the value of a case
expression with a let
binding.
let description =
case True {
True -> "It's true!"
False -> "It's not true."
}
description // => "It's true!"
Destructuring
A case
expression can be used to destructure values that
contain other values, such as tuples and lists.
case xs {
[] -> "This list is empty"
[a] -> "This list has 1 element"
[a, b] -> "This list has 2 elements"
_other -> "This list has more than 2 elements"
}
It's not just the top level data structure that can be pattern matched,
contained values can also be matched. This gives case
the ability to
concisely express flow control that might be verbose without pattern matching.
case xs {
[[]] -> "The only element is an empty list"
[[], ..] -> "The 1st element is an empty list"
[[4], ..] -> "The 1st element is a list of the number 4"
other -> "Something else"
}
Pattern matching also works in let
bindings, though patterns that do not
match all instances of that type may result in a runtime error.
let [a] = [1] // a is 1
let [b] = [1, 2] // Runtime error! The pattern has 1 element but the value has 2
Matching on multiple values
Sometimes it is useful to pattern match on multiple values at the same time,
so case
supports having multiple subjects.
case x, y {
1, 1 -> "both are 1"
1, _ -> "x is 1"
_, 1 -> "y is 1"
_, _ -> "neither is 1"
}
Assigning names to sub-patterns
Sometimes when pattern matching we want to assign a name to a value while
specifying its shape at the same time. We can do this using the as
keyword.
case xs {
[[_, ..] as inner_list] -> inner_list
other -> []
}
Checking equality and ordering in patterns
The if
keyword can be used to add a guard expression to a case clause. Both
the patterns have to match and the guard has to evaluate to True
for the
clause to match. The guard expression can check for equality or ordering for
Int
and Float
.
case xs {
[a, b, c] if a == b && b != c -> "ok"
_other -> "ko"
}
case xs {
[a, b, c] if a >. b && a <=. c -> "ok"
_other -> "ko"
}
Alternative clause patterns
Alternative patterns can be given for a case clause using the |
operator. If
any of the patterns match then the clause matches.
Here the first clause will match if the variable number
holds 2, 4, 6 or 8.
case number {
2 | 4 | 6 | 8 -> "This is an even number"
1 | 3 | 5 | 7 -> "This is an odd number"
_ -> "I'm not sure"
}
If the patterns declare variables then the same variables must be declared in all patterns, and the variables must have the same type in all the patterns.
case list {
[1, x] | x -> x // Error! Int != List(Int)
_ -> 0
}
Functions
Named functions
Named functions in Gleam are defined using the pub fn
keywords.
pub fn add(x: Int, y: Int) -> Int {
x + y
}
pub fn multiply(x: Int, y: Int) -> Int {
x * y
}
Functions in Gleam are first class values and so can be assigned to variables, passed to functions, or anything else you might do with any other data type.
/// This function takes a function as an argument
pub fn twice(f: fn(t) -> t, x: t) -> t {
f(f(x))
}
pub fn add_one(x: Int) -> Int {
x + 1
}
pub fn add_two(x: Int) -> Int {
twice(add_one, x)
}
Pipe Operator
Gleam provides syntax for passing the result of one function to the arguments of another function, the pipe operator (|>
). This is similar in functionality to the same operator in Elixir or F#.
The pipe operator allows you to chain function calls without using a plethora of parenthesis. For a simple example, consider the following implementation of string.reverse
in Gleam:
string_builder.to_string(string_builder.reverse(string_builder.new(string)))
This can be expressed more naturally using the pipe operator, eliminating the need to track parenthesis closure.
string
|> string_builder.new
|> string_builder.reverse
|> string_builder.to_string
Each line of this expression applies the function to the result of the previous line. This works easily because each of these functions take only one argument. Syntax is available to substitute specific arguments of functions that take more than one argument; for more, look below in the section "Function capturing".
Type annotations
Function arguments are normally annotated with their type, and the compiler will check these annotations and ensure they are correct.
fn identity(x: some_type) -> some_type {
x
}
fn inferred_identity(x) {
x
}
The Gleam compiler can infer all the types of Gleam code without annotations and both annotated and unannotated code is equally safe. It's considered a best practice to always write type annotations for your functions as they provide useful documentation, and they encourage thinking about types as code is being written.
Generic functions
At times you may wish to write functions that are generic over multiple types. For example, consider a function that consumes any value and returns a list containing two of the value that was passed in. This can be expressed in Gleam like this:
fn list_of_two(my_value: a) -> List(a) {
[my_value, my_value]
}
Here the type variable a
is used to represent any possible type.
You can use any number of different type variables in the same function. This
function declares type variables a
and b
.
fn multi_result(x: a, y: b, condition: Bool) -> Result(a, b) {
case condition {
True -> Ok(x)
False -> Error(y)
}
}
Type variables can be named anything, but the names must be lower case and may contain underscores. Like other type annotations, they are completely optional, but may aid in understanding the code.
Labelled arguments
When functions take several arguments it can be difficult for the user 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.
Take this function that replaces sections of a string:
pub fn replace(string: String, pattern: String, replacement: String) {
// ...
}
It can be given labels like so.
pub fn replace(
in string: String,
each pattern: String,
with replacement: String,
) {
// The variables `string`, `pattern`, and `replacement` are in scope here
}
These labels can then be used when calling the function.
replace(in: "A,B,C", each: ",", with: " ")
// Labelled arguments can be given in any order
replace(each: ",", with: " ", in: "A,B,C")
// Arguments can still be given in a positional fashion
replace("A,B,C", ",", " ")
The use of argument labels can allow a function to be called in an expressive, sentence-like manner, while still providing a function body that is readable and clear in intent.
Anonymous functions
Anonymous functions can be defined with a similar syntax.
pub fn run() {
let add = fn(x, y) { x + y }
add(1, 2)
}
Function capturing
There is a shorthand syntax for creating anonymous functions that take one
argument and call another function. The _
is used to indicate where the
argument should be passed.
pub fn add(x, y) {
x + y
}
pub fn run() {
let add_one = add(1, _)
add_one(2)
}
The function capture syntax is often used with the pipe operator to create a series of transformations on some data.
pub fn add(x: Int , y: Int ) -> Int {
x + y
}
pub fn run() {
// This is the same as add(add(add(1, 3), 6), 9)
1
|> add(_, 3)
|> add(_, 6)
|> add(_, 9)
}
In fact, this usage is so common that there is a special shorthand for it.
pub fn run() {
// This is the same as the example above
1
|> add(3)
|> add(6)
|> add(9)
}
The pipe operator will first check to see if the left hand value could be used
as the first argument to the call, e.g. 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)
.
Documentation
You may add user facing documentation in front of function definitions with a
documentation comment ///
per line. Markdown is supported and this text
will be included with the module's entry in generated HTML documentation.
/// Does nothing, returns `Nil`.
///
fn returns_nil(a) -> Nil {
Nil
}
Modules
Gleam programs are made up of bundles of functions and types called modules. Each module has its own namespace and can export types and values to be used by other modules in the program.
// inside module src/nasa/rocket_ship.gleam
fn count_down() {
"3... 2... 1..."
}
fn blast_off() {
"BOOM!"
}
pub fn launch() {
[
count_down(),
blast_off(),
]
}
Here we can see a module named nasa/rocket_ship
, the name determined by the
filename src/nasa/rocket_ship.gleam
. Typically all the modules for one
project would live within a directory with the name of the project, such as
nasa
in this example.
For the functions count_down
and blast_off
we have omitted the pub
keyword, so these functions are private module functions. They can only be
called by other functions within the same module.
Import
To use functions or types from another module we need to import them using the
import
keyword.
// inside module src/nasa/moon_base.gleam
import nasa/rocket_ship
pub fn explore_space() {
rocket_ship.launch()
}
The statement import nasa/rocket_ship
creates a new variable with the name
rocket_ship
and the value of the rocket_ship
module.
In the explore_space
function we call the imported module's public launch
function using the .
operator. If we had attempted to call count_down
it
would result in a compile time error as this function is private to the
rocket_ship
module.
Named import
It is also possible to give a module a custom name when importing it using the
as
keyword.
import unix/cat
import animal/cat as kitty
This may be useful to differentiate between multiple modules that would have the same default name when imported.
Unqualified import
Values and types can also be imported in an unqualified fashion.
import animal/cat.{Cat, stroke}
pub fn main() {
let kitty = Cat(name: "Nubi")
stroke(kitty)
}
This may be useful for values that are used frequently in a module, but generally qualified imports are preferred as it makes it clearer where the value is defined.
The prelude module
There is one module that is built into the language, the gleam
prelude
module. By default its types and values are automatically imported into
every module you write, but you can still chose to import it the regular way.
This may be useful if you have created a type or value with the same name as
an item from the prelude.
import gleam
/// This definition locally overrides the `Result` type
/// and the `Ok` constructor.
pub type Result {
Ok
}
/// The original `Result` and `Ok` can still be used
pub fn go() -> gleam.Result(Int) {
gleam.Ok(1)
}
The prelude module contains these types:
BitString
Bool
Float
Int
List(element)
Nil
Result(value, error)
String
UtfCodepoint
And these values:
Error
False
Nil
Ok
True
Documentation
You may add user facing documentation at the head of modules with a module
documentation comment ////
per line. Markdown is supported and this text
will be included with the module's entry in generated HTML documentation.
Custom types
Gleam's custom types are named collections of keys and values. They are similar to objects in object oriented languages, though they don't have methods.
Custom types are defined with the type
keyword.
pub type Cat {
Cat(name: String, cuteness: Int)
}
Here we have defined a custom type called Cat
. Its constructor is called
Cat
and it has two fields: A name
field which is a String
, and a
cuteness
field which is an Int
.
The pub
keyword makes this type usable from other modules.
Once defined the custom type can be used in functions:
fn cats() {
// Labelled fields can be given in any order
let cat1 = Cat(name: "Nubi", cuteness: 2001)
let cat2 = Cat(cuteness: 1805, name: "Biffy")
// Alternatively fields can be given without labels
let cat3 = Cat("Ginny", 1950)
[cat1, cat2, cat3]
}
Multiple constructors
Custom types in Gleam can be defined with multiple constructors, making them a way of modeling data that can be one of a few different variants.
We've already seen a custom type with multiple constructors in the Language
Tour - Bool
.
Gleam's built-in Bool
type is defined like this:
// A Bool is a value that is either `True` or `False`
pub type Bool {
True
False
}
It's a simple custom type with constructors that takes no arguments at all!
Use it to answer yes/no questions and to indicate whether something is True
or False
.
The records created by different constructors for a custom type can contain
different values. For example a User
custom type could have a LoggedIn
constructor that creates records with a name, and a Guest
constructor which
creates records without any contained values.
type User {
LoggedIn(name: String) // A logged in user with a name
Guest // A guest user with no details
}
let sara = LoggedIn(name: "Sara")
let rick = LoggedIn(name: "Rick")
let visitor = Guest
Destructuring
When given a custom type record we can pattern match on it to determine which record constructor matches, and to assign names to any contained values.
fn get_name(user) {
case user {
LoggedIn(name) -> name
Guest -> "Guest user"
}
}
Custom types can also be destructured with a let
binding.
type Score {
Points(Int)
}
let score = Points(50)
let Points(p) = score
p // => 50
During destructuring you may also use discards (_
) or spreads (..
).
pub type Cat {
Cat(name: String, cuteness: Int, age: Int)
}
let cat = Cat(name: "Felix", cuteness: 9001, age: 5)
You will need to specify all args for a pattern match, or alternatively use the spread operator.
// All fields present
let Cat(name: name, cuteness: _, age: _) = cat
name // "Felix"
// Other fields ignored by spreading
let Cat(age: age, ..) = cat
age // 5
Named accessors
If a custom type has only one variant and named fields they can be accessed
using .field_name
.
For example using the Cat
type defined earlier.
let cat = Cat(name: "Nubi", cuteness: 2001)
cat.name // This returns "Nubi"
cat.cuteness // This returns 2001
Generics
Custom types can be be parameterised with other types, making their contents variable.
For example, this Box
type is a simple record that holds a single value.
pub type Box(inner_type) {
Box(inner: inner_type)
}
The type of the field inner
is inner_type
, which is a parameter of the Box
type. If it holds an int the box's type is Box(Int)
, if it holds a string the
box's type is Box(String)
.
pub fn main() {
let a = Box(123) // type is Box(Int)
let b = Box("G") // type is Box(String)
}
Opaque types
At times it may be useful to create a type and make the constructors and fields private so that users of this type can only use the type through publically exported functions.
For example we can create a Counter
type which holds an int which can be
incremented. We don't want the user to alter the int value other than by
incrementing it, so we can make the type opaque to prevent them from being
able to do this.
// The type is defined with the opaque keyword
pub opaque type Counter {
Counter(value: Int)
}
pub fn new() {
Counter(0)
}
pub fn increment(counter: Counter) {
Counter(counter.value + 1)
}
Because the Counter
type has been marked as opaque
it is not possible for
code in other modules to construct or pattern match on counter values or
access the value
field. Instead other modules have to manipulate the opaque
type using the exported functions from the module, in this case new
and
increment
.
Record updates
Gleam provides a dedicated syntax for updating some of the fields of a custom type record.
import gleam/option.{Option}
pub type Person {
Person(
name: String,
gender: Option(String),
shoe_size: Int,
age: Int,
is_happy: Bool,
)
}
pub fn have_birthday(person) {
// It's this person's birthday, so increment their age and
// make them happy
Person(..person, age: person.age + 1, is_happy: True)
}
As Gleam records are immutable the update syntax does not alter the fields in place, instead it created a new record with the values of the initial record with the new values added.
Erlang interop
At runtime custom type records with no contained values become atoms. The
atoms are written in snake_case
rather than CamelCase
so LoggedIn
becomes logged_in
.
Custom type records with contained values are Erlang records. The Gleam compiler generates an Erlang header file with a record definition for each constructor, for use from Erlang.
// Gleam
Guest
LoggedIn("Kim")
# Elixir
:guest
{:logged_in, "Kim"}
% Erlang
guest,
{logged_in, <<"Kim">>}.
Result(value, error)
pub type Result(value, reason) {
Ok(value)
Error(reason)
}
Gleam doesn't have exceptions or null
to represent errors in our programs,
instead we have the Result
type. If a function call fails, wrap the returned
value in a Result
, either Ok
if the function was successful, or Error
if it failed.
pub fn lookup(name, phone_book) {
// ... we found a phone number in the phone book for the given name here
Ok(phone_number)
}
The Error
type needs to be given a reason for the failure in order to
return, like so:
pub type MyDatabaseError {
InvalidQuery
NetworkTimeout
}
pub fn insert(db_row) {
// ... something went wrong connecting to a database here
Error(NetworkTimeout)
}
In cases where we don't care about the specific error enough to want to create
a custom error type, or when the cause of the error is obvious without further
detail, the Nil
type can be used as the Error
reason.
pub fn lookup(name, phone_book) {
// ... That name wasn't found in the phone book
Error(Nil)
}
When we have a Result
type returned to us from a function we can pattern
match on it using case
to determine whether we have an Ok
result or
an Error
result.
The standard library gleam/result
module contains helpful functions for
working with the Result
type, make good use of them!
Try
In Gleam if a function can either succeed or fail then it normally will
return the Result
type. With Result
, a successful return value is wrapped
in an Ok
record, and an error value is wrapped in an Error
record.
// parse_int(String) -> Result(Int, String)
parse_int("123") // -> Ok(123)
parse_int("erl") // -> Error("expected a number, got `erl`")
When a function returns a Result
we can pattern match on it to handle success
and failure:
case parse_int("123") {
Error(e) -> io.println("That wasn't an Int")
Ok(i) -> io.println("We parsed the Int")
}
This is such a common pattern in Gleam that the try
syntax exists to make it
more concise.
try int_a = parse_int(a)
try int_b = parse_int(b)
try int_c = parse_int(c)
Ok(int_a + int_b + int_c)
When a variable is declared using try
Gleam checks to see whether the value
is an Error or an Ok record. If it's an Ok then the inner value is assigned to
the variable:
try x = Ok(1)
Ok(x + 1)
// -> Ok(2)
If it's an Error then the Error is returned immediately:
try x = Error("failure")
Ok(x + 1)
// -> Error("failure")
Assert
Some times we have a function that can technically fail, but in practice we don't expect it to happen. For example our program may start by opening a file, if we know that the file is always going to be possible to open, then we don't want to complicate our program with handling an error that should never happen.
Other times we have errors that may occur, but we don't have any way of realistically handling them within our program. For example if we have a web application that talks to a database when handling each HTTP request and that database stops responding, then we have a fatal error that cannot be recovered from. We could detect the error in our code, but what do we do then? We need the database to handle the request.
Lastly we may think errors are possible, but we are writing a quick script or prototype application, so we want to only spend time on the success path for now.
For these situations Gleam provides assert
, a keyword that causes the
program to crash if a pattern does not match.
assert Ok(i) = parse_int("123")
i // => 123
Here the assert
keyword has been used to say "this function must return an
Ok
value" and we haven't had to write any error handling. The inner value
is assigned the variable i
and the program continues.
assert Ok(i) = parse_int("not an int")
In this case the parse_int
function returns an error, so the Ok(i)
pattern doesn't match and so the program crashes.
Surviving crashes
Being fault tolerant and surviving crashes is a key part of Erlang's error handling strategy, and as an Erlang based language Gleam can also take advantage of this. To find out more about Erlang fault tolerance see the Gleam OTP project and the Learn You Some Erlang chapter on supervisors.
Todo
Gleam's todo
keyword is used to indicate that some code is not yet finished.
It can be useful when designing a module, type checking functions and types but leaving the implementation of the functions until later.
fn favourite_number() -> Int {
// The type annotations says this returns an Int, but we don't need
// to implement it yet.
todo
}
pub fn main() {
favourite_number() * 2
}
When this code is built Gleam will type check and compile the code to ensure
it is valid, and the todo
will be replaced with code that crashes the
program if that function is run.
A message can be given as a form of documentation. The message will be printed
in the error message when the todo
code is run.
fn favourite_number() -> Int {
todo("We're going to decide which number is best tomorrow")
}
When the compiler finds a todo
it will print a warning, which can be useful
to avoid accidentally forgetting to remove a todo
.
The warning also includes the expected type of the expression that needs to
replace the todo
. This can be a useful way of asking the compiler what type
is needed if you are ever unsure.
fn main() {
my_complicated_function(
// What type does this function take again...?
todo
)
}
Constants
Gleam's module constants provide a way to use a certain fixed value in multiple places in a Gleam project.
pub const start_year = 2101
pub const end_year = 2111
pub fn is_before(year: Int) -> Bool {
year < start_year
}
pub fn is_during(year: Int) -> Bool {
start_year <= year && year <= end_year
}
Like all values in Gleam constants are immutable and their values cannot be changed, so they cannot be used as global mutable state.
When a constant is referenced the value is inlined by the compiler, so they can be used in case expression guards.
pub const start_year = 2101
pub const end_year = 2111
pub describe(year: Int) -> String {
case year {
year if year < start_year -> "Before"
year if year > end_year -> "After"
_ -> "During"
}
}
Type annotations
Constants can also be given type annotations.
pub const name: String = "Gleam"
pub const size: Int = 100
These annotations serve as documentation or can be used to provide a more specific type than the compiler would otherwise infer.
Type aliases
Type aliases are a way of creating a new name for an existing type. This is useful when the name of the type may be long and awkward to type repeatedly.
Here we are giving the type List(#(String, String))
the new name
Headers
. This may be useful in a web application where we want to write
multiple functions that return headers.
pub type Headers =
List(#(String, String))
Bit strings
Gleam has a convenient syntax for working directly with binary data called a Bit String. Bit Strings represent a sequence of 1s and 0s.
Bit Strings are written literally with opening brackets <<
, any number of bit
string segments separated by commas, and closing brackets >>
.
Bit String Segments
By default a Bit String segment represents 8 bits, also known as 1 byte.
// This is the number 3 as an 8 bit value.
// Written in binary it would be 00000011
<<3>>
You can also specify a bit size using either short hand or long form.
// These are the exact same value as above
// Shorthand
<<3:8>>
// Long Form
<<3:size(8)>>
You can specify any positive integer as the bit size.
// This is not same as above, remember we're working with a series of 1s and 0s.
// This Bit String is 16 bits long: 0000000000000011
<<3:size(16)>>
You can have any number of segments separated by commas.
// This is True
<<0:4, 1:3, 1:1>> == <<3>>
Bit String Segment Options
There are a few more options you can attach to a segment to describe its size and bit layout.
unit()
lets you create a segment of repeating size. The segment will
represent unit * size
number of bits. If you use unit()
you must also have
a size
option.
// This is True
<<3:size(4)-unit(4)>> == <<3:size(16)>>
The utf8
, utf16
and utf32
options let you put a String directly into a
Bit String.
<<"Hello Gleam 💫":utf8>>
The bit_string
option lets you put any other Bit String into a Bit String.
let a = <<0:1, 1:1, 1:1>>
<<a:bit_string, 1:5>> == <<"a":utf8>> // True
Here Is the full list of options and their meaning:
Options in Values
Option | Meaning |
---|---|
bit_string | a bitstring that is any bit size |
float | default size of 64 bits |
int | default size of 8 bits |
size | the size of the segment in bits |
unit | how many times to repeat the segment, must have a size |
big | big endian |
little | little endian |
native | endianness of the processor |
utf8 | a string to encode as utf8 codepoints |
utf16 | a string to encode as utf16 codepoints |
utf32 | a string to encode as utf32 codepoints |
Options in Patterns
Option | Meaning |
---|---|
binary | a bitstring that is a multiple of 8 bits |
bit_string | a bitstring that is any bit size |
float | float value, size of exactly 64 bits |
int | int value, default size of 8 bits |
big | big endian |
little | little endian |
native | endianness of the processor |
signed | the captured value is signed |
unsigned | the captured value is unsigned |
size | the size of the segment in bits |
unit | how many times to repeat the segment, must have a size |
utf8 | an exact string to match as utf8 codepoints |
utf16 | an exact string to match as utf16 codepoints |
utf32 | an exact string to match as utf32 codepoints |
utf8_codepoint | a single valid utf8 codepoint |
utf16_codepoint | a single valid utf16 codepoint |
utf32_codepoint | a single valid utf32 codepoint |
Values vs Patterns
Bit Strings can appear on either the left or the right side of an equals sign. On the left they are called patterns, and on the right they are called values.
This is an important distinction because values and patterns have slightly different rules.
Rules for Patterns
You can match on a variable length segment with the bit_string
or binary
options. A pattern can have at most 1 variable length segment and it must be
the last segment.
In a pattern the types utf8
, utf16
, and utf32
must be an exact string.
They cannot be a variable. There is no way to match a variable length section
of a binary with an exact encoding.
You can match a single variable codepoint with utf8_codepoint
,
utf16_codepoint
, and utf32_codepoint
which will match the correct number of
bytes depending on the codepoint size and data.
Further Reading
Gleam inherits its Bit String syntax and handling from Erlang. You can find the Erlang documentation here.
External function
Gleam is just one of many languages on the Erlang virtual machine and JavaScript runtimes. At times we may want to use functions from these other languages in our Gleam programs. To enable this Gleam allows the importing of external functions, which may be written in any language on the same runtime.
External functions are typically written in a different language with a different type system, so the compiler is unable to determine the type of the function and instead the programmer must inform the compiler the type.
Gleam trusts that the type given is correct so an inaccurate type annotation can result in unexpected behaviour and crashes at runtime. Be careful!
Erlang external functions
The Erlang rand
module has a function named uniform
that takes no
arguments and returns a Float
.
The Elixir module IO
has a function named inspect
that takes any value,
prints it, and returns the same value.
If we want to import these functions and use them in our program we would do so like this:
pub external fn random_float() -> Float =
"rand" "uniform"
// Elixir modules start with `Elixir.`
pub external fn inspect(a) -> a =
"Elixir.IO" "inspect"
JavaScript external functions
When importing a JavaScript function the path to the module is given instead of the module name.
// In src/my-module.mjs
export function run() {
return 0;
}
// In src/my_program.gleam
pub external fn run() -> Int =
"./my-module.js" "run"
Gleam uses the JavaScript import syntax, so any module imported must use the
esmodule export syntax, and if you are using the NodeJS runtime the file
extension must be .mjs
.
Labelled arguments
Like regular functions, external functions can have labelled arguments.
pub external fn any(in: List(a), satisfying: fn(a) -> Bool) =
"my_external_module" "any"
This function has the labelled arguments in
and satisfying
, and can be
called like so:
any(in: my_list, satisfying: is_even)
any(satisfying: is_even, in: my_list)
External type
In addition to importing external functions we can also import external types. Gleam knows nothing about the runtime representation of these types and so they cannot be pattern matched on, but they can be used with external functions that know how to work with them.
Here is an example of importing a Queue
data type and some functions from
Erlang's queue
module to work with the new Queue
type.
pub external type Queue(a)
pub external fn new() -> Queue(a) = "queue" "new"
pub external fn length(Queue(a)) -> Int = "queue" "len"
pub external fn push(Queue(a), a) -> Queue(a) = "queue" "in"