BerandaComputers and TechnologyGleam v0.12 and Gleam OTP v0.1 released

Gleam v0.12 and Gleam OTP v0.1 released

It’s halloween and time for another Gleam release! This time it’s an extra
special release as it also includes the first version of Gleam’s type safe
actor system
, compatible with Erlang’s OTP.

Mutual recursion

Historically functions in Gleam had to be defined in a module prior to being used.

pub fn main() {
  helper() // Compile error! Function `helper` not found
}

fn helper() {
  0
}

This has proven to be an annoyance as even though our code should compile
(all the required functions have been defined in the module) the compiler
won’t succeed unless we carefully maintain a specific order of functions in
the module.

This limitation also forced the programmer to write any helper functions at
the top of the file, before the functions that make use of them. Many people
like to write their code in the opposite order with their entry-point at the
top (such as a main function), and then helper functions below. That way
the module reads top-to-bottom, introducing new functions in the order they
are used.

Lastly it also meant that two functions could not call each other, making
useful techniques such as mutual recursion impossible.

With this release statements in a module can be written in any order, and
they still don’t require any type annotations to be fully type checked.

pub fn barry() {
  io.println("To you!")
  paul()
}

pub fn paul() {
  io.println("To me!")
  barry()
}

That’s the only major change to the language this time. For a full list of
changes see the compiler changelog.

Next up, Gleam’s new type safe actor system!

Gleam OTP

Gleam’s actor system is built with a few primary goals:

  • Full type safety of actors and messages.
  • Be compatible with Erlang’s OTP actor framework.
  • Provide fault tolerance and self-healing through supervisors.
  • Have equivalent performance to Erlang’s OTP.

Two notable non-goals are that we are not explicitly supporting the BEAM’s
hot code upgrades, and we do not provide type safety for distributed
computing where two BEAM nodes on different computers send messages to each
other.

The core concepts of Gleam OTP are processes, channels, actors, and
supervisors. Let’s take a look at each of them.

Processes

The core concurrency primitive in Gleam OTP is the process, which is a
lightweight green thread implemented by the BEAM virtual machine. These
processes are extremely cheap to create and run, and a single BEAM instance
can happily run millions of processes at the same time. It’s highly
sophisticated preemptive scheduler will ensure they make full use of all the
cores of the CPU, and no malicious or buggy process can block others from
running.

There’s a wealth of information online about the BEAM’s processes so we won’t
cover it all here, but know that Gleam’s actor system performs well thanks to
decades of excellent work by the Erlang community.

Use processes in Gleam we import the gleam/otp/process module which
provides functions for creating and working with them.

import gleam/io
import gleam/otp/process

pub fn main() {
  process.start(fn() {
    io.println("Hello from the new process!")
  })

  io.println("Hello from the parent process!")
}

In this code snippet we create a new process using the
process.start function, before printing a message to
the console.

The newly created process also prints a message, but because the two
processes run asynchronously (and likely run on different CPU cores) we don’t
know which will be printed first. It could be the new one first, or it could
be the parent, and it could be different each time the program runs.

One important thing to note is that there’s no need for an async/await
syntax or callbacks. Asynchronous code in Gleam looks exactly the same to
regular synchronous code and doesn’t require any special types such as
futures or promises.

This should hopefully feel familiar to people familiar with concurrent
programming in languages such as Erlang, Elixir, and Go.

Channels

It’s not good enough to merely be able to create processes, we need processes
to be able to communicate and cooperate. Erlang and Gleam don’t have mutable
state so locks and shared mutable state is not an option- instead Gleam uses
channels.

Channels allow one process to send a message to another in a type safe
fashion, and they may be familiar to Go and Rust programmers.

A channel is made of a Sender, which messages as sent into,
and a Receiver, which messages are pulled out of by the
channel owner process.

import gleam/io
import gleam/otp/process

pub fn main() {
  // Create a new channel owned by the current process
  let tuple(sender, receiver) = process.new_channel()

  process.start(fn() {
    io.println("Hello from the new process!")
    // Send a message to the parent process
    process.send(sender, "Done")
  })

  // Receive a message from the child, waiting up to 100ms
  process.receive(receiver, 100) // This returns Ok("Done")
  io.println("Hello from the parent process!")
}

Here the code has been updated to use a channel to coordinate the processes.
The parent process creates a channel and waits for the child to send a
message over the channel before printing. Because of this we know that the
child process will now always print to the console before the parent does.

Here we’re just using a simple string message to synchronize processes but
any type we want can be sent over channel, enabling sharing of data in
concurrent programs.

The Gleam compiler can full type check channels. Because the message sent
with the sender is a String the compiler knows messages pulled from the
receiver must also be strings and will print a helpful error with any program
that doesn’t use these messages correctly.

Actors

In programs written in Erlang using OTP we tend to not use raw processes
frequently, instead we use a higher level abstraction called gen_server
that builds upon a process to provide additional features making it more
suited to being used in a long-lived Erlang application. Gleam is similar,
though our higher level abstraction is called actor and
differs in design to gen_server in order to provide type safety.

import gleam/io
import gleam/otp/actor
import gleam/otp/process.{Sender}

pub type Message {
  Request(reply_channel:  Sender(String))
}

pub fn main() {
  let actor = actor.start(0, handle_message)

  // Send a message to the child and wait for a response
  process.call(actor, Request, 100) // This returns Ok("Done")
  io.println("Hello from the parent process!")
}

fn handle_message(msg:  Message, state) {
  io.println("The actor got a message")
  process.send(msg.reply_channel, "Done")
  actor.Continue(state)
}

Here the code has been adapted to use an actor rather than a low-level
process, and instead of explicitly creating a new channel we use the
call function. This function is similar to Erlang’s
gen_server:call in that it provides a way to send a message to a process
and then wait until a reply is received.

Since an actor is being used here rather than a raw process it doesn’t shut
down after running the function once, instead the handler function is called
once per message received and the actor continues to run until either it is
requested to shut down or it returns actor.Stop from its handler function.

Any of OTP’s debug or system messages sent to the actor are automatically
handled by the actor implementation, while they would need to be manually
handled when using a raw process.

Supervisors

Supervisors are special actors that start other actors and then monitor them
to ensure they are healthy. If one of the children crashes the supervisor
restarts it along with any younger children, restoring the program to a
healthy state now that any transient problems have ended.

If the problem is not transient and the child continues to crash then the
supervisor will shut down and then the supervisor’s supervisor can attempt a
restart now that more possibly invalid or corrupt state has been reset.

By using this supervision functionality OTP applications can achieve high
reliability by shedding state in an incremental fashion until the program
self-heals. This is kind-of similar to how Kubernetes and other container
orchestrators handle failure in microservices, though faster and more precise
thanks to it being integrated at all levels of the program.

import gleam/otp/supervisor.{add, returning, worker}
import my_app/monitoring
import my_app/database
import my_app/web

pub fn main() {
  supervisor.start(fn(children) {
    children
    |> add(worker(database.start))
    |> add(worker(monitoring.start))
    |> add(worker(web.start))
  })
}

Here we have a supervisor for a pretend Gleam web application. It has 3
children of the type worker, a database connection, an actor responsible
for monitoring, and a HTTP handling actor. If we were adding a supervisor
child we could use the supervisor function instead of worker.

Going forward

Gleam OTP v0.1 is the result of approaching 2 years of research and
implementation, mostly focusing on how to strike a good balance between type
safety and compatibility with existing Erlang OTP patterns.

Rather than build on top of Erlang’s supervisor and gen_server Gleam OTP
has a small core written in Erlang which implements the Receiver half of
channels, and the rest is implemented in Gleam. Thanks to this we can have
greater confidence in the viability of the core abstractions as they have
been sufficient to implement the rest of Gleam OTP in a type safe fashion.

These APIs (particularly supervisor) are experimental and open to breaking
changes as we discover new ways to improve them, but we’ve reach a point
where we can start writing programs using it.

If you write something using Gleam OTP please do get in touch and let us know
how you find it!

Discord chat

Lastly, we’ve got a new home for the community! The Gleam IRC channel has
been replaced by the Gleam Discord chat server to great success. Since
opening we’ve seen a big increase in activity and lots of new exciting Gleam
projects. If you’d like to join click here!.

Try it out

If you want to try out the new version of Gleam head over to the installation
page
. I’d love to hear how you find it and get your feedback so
Gleam can continue to improve.

Want to view some existing Gleam projects? Head on over to the
awesome-gleam list. Looking for something to build in
Gleam? Check out the suggestions tracker.

Supporting Gleam

If you would like to help make strongly typed programming on the Erlang
virtual machine a production-ready reality please consider sponsoring
Gleam
via the GitHub Sponsors program.

This release would not have been possible without the support of all the
people who have sponsored and contributed
to it, so a huge thank you to them.

Lastly, a huge thank you to the contributors to and sponsors of Gleam since
last release!

Thanks for reading! Have fun! 💜

Read More

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments