Gleam calling Erlang and v.v.

Feb 11, 2023   Kero van Gelder

This post is part of a series of four, this post, A Rebar3 project using Gleam, Gleam calling JS and v.v., and Gleam in the browser.

This post looks at Gleam code calling Erlang, and Erlang code calling Gleam.

Gleam FFI to Erlang

The first target of Gleam was Erlang. It should come as no surprise that the interoperability of Gleam and Erlang is super smooth.

Let's get started!

gleam new happy
cd happy

To let Gleam call into Erlang, we use the Foreign Function Interface, commonly abbreviated to 'ffi'. Add a line @external(erlang, "module", "function") above a function declaration, with types, and omit the implementation.

Edit src/happy.gleam so it contains:

import gleam/io

pub fn main() {
  [1, 2, 3]
  |> erlang_reverse_ints
  |> io.debug
}

@external(erlang, "lists", "reverse")
pub fn erlang_reverse_ints(list: List(Int)) -> List(Int)

We are literally calling fun lists:reverse/1. This Erlang function accepts a list with any, mixed types of elements. Gleam is typed, so we must specify the type of all arguments we are passing to the Erlang function, and the type of the value that it returns. Gleam types map directly to Erlang types, which makes calling, well, straightforward. There is a list of type mappings for your reference.

Let's run it with gleam run, this will show us [3, 2, 1].

! Note that when during run-time, the Erlang function returns a value of a different type, you are on your own. The type checker of Gleam is compile-time, not run-time.

To tap into the dynamic types of Erlang a bit more, we can specify that we pass a list of any type, and we will get a list of the same type:

import gleam/io

pub fn main() {
  reverse_and_debug([1, 2, 3])
  reverse_and_debug(["hello", "world"])
}

pub fn reverse_and_debug(list: List(a)) -> List(a) {
  list
  |> erlang_reverse
  |> io.debug
}

@external(erlang, "lists", "reverse")
pub fn erlang_reverse(list: List(a)) -> List(a)

and then we get:

$ gleam run
  Compiling happy
   Compiled in 0.09s
    Running happy.main
  [3, 2, 1]
  ["world", "hello"]

Erlang calling Gleam

The above code is compiled to Erlang code, I found it in build/dev/erlang/happy/_gleam_artefacts/happy.erl:
-module(happy).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).

-export([erlang_reverse/1, reverse_and_debug/1, main/0]).

-spec erlang_reverse(list(WR)) -> list(WR).
erlang_reverse(List) ->
    lists:reverse(List).

-spec reverse_and_debug(list(WO)) -> list(WO).
reverse_and_debug(List) ->
    _pipe = List,
    _pipe@1 = lists:reverse(_pipe),
    gleam@io:debug(_pipe@1).

-spec main() -> list(binary()).
main() ->
    reverse_and_debug([1, 2, 3]),
    reverse_and_debug([<<"hello"/utf8>>, <<"world"/utf8>>]).
which means you can simply call our Gleam function happy.reverse_and_debug([1, 2, 3]) from Erlang as happy:reverse_and_debug([1, 2, 3]).

For completeness sake, you can see that our 'ffi' function is indeed literally calling the Erlang function.

[Added Mar 16] The easiest place to have Erlang code that can call Gleam code is directly in your ./src folder. Gleam not only compiles .gleam files, but also picks up .erl files if the project target is Erlang.

How to get this compiled Gleam/Erlang code into your Erlang/rebar3 project is worth a blog post by itself.