Externals should be used sparingly.

Always prefer Gleam based solutions, using externals only when there is no suitable alternative for your needs and development constraints.

What are externals?

Gleam's external type and external function features lets Gleam code make use of code written in other languages, providing solutions when there is not a pure-Gleam library readily available for a given task.

The languages that can be used depends on the compilation target used for the Gleam code.

There is no additional performance cost to calling functions written in other languages and those written in Gleam.

Defining external functions

External functions are functions that are written in another language and then imported into Gleam code, where they can be called like a normal Gleam function.

External functions are written as a bodiless function with the @external attribute. The attribute takes 3 arguments, indicating where to find the function written in the non-Gleam language.

For example, here's an external function that imports the reverse function from the Erlang lists module.

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

Type annotations are not optional for external functions, they must always be written. The Gleam compiler will ensure that all uses of the function will be correct for the annotated types, but it cannot verify that the function implemented in the other language returns the specified types, or even that it exists. Great care must be taken when defining external functions to ensure that the @external attribute and type annotations are correct.

You may wish to write more unit tests than usual when using external functions.

Defining external types

Gleam code can make use of data types defined in other languages using the "external types" feature. The syntax is similar to the custom type syntax, except no variants are specified.

pub type ErlangReference

Here an external type has been defined with the name ErlangReference, which will be used to represent Erlang's reference() type. The name used in Gleam doesn't have to match the name in the external language, it could be anything at all.

Gleam doesn't know anything about this type other than its existence and the name it has been given in the definition, so it cannot be constructed or manipulated directly in Gleam code,External functions must be used instead.

pub type ErlangReference

@external(erlang, "erlang", "make_ref")
pub fn make_reference() -> ErlangReference

The make_reference external function has been defined to construct references. Similar functions could be defined that take references as arguments.

Note that while the Erlang reference type is being used in this example, a real Gleam program that uses references would use the gleam/erlang/reference module from the gleam_erlang package, or define a more precise type for the subset of operations these references are usable for. e.g. TransactionId or MessageTag.

Erlang externals

Erlang is the most straightforward language to declare external functions for. The module and function names are written the same in the Erlang code as in the Gleam external definition. Any public function in any module can be used as an external.

-module(pokemon).
-export([badge_count/0]).

badge_count() ->
    8.

This is an Erlang module named pokemon, with a public function called badge_count, which takes no arguments. The accompanying external function in Gleam would look like this:

@external(erlang, "pokemon", "badge_count")
pub fn pokemon_badge_count() -> Int

Erlang macros are not usable outside of Erlang.

Gleam data in Erlang

Gleam data types are also common Erlang data types, making working with Gleam data in Erlang relatively straightforward.

Any Erlang type not listed below would be represented in Gleam using an external type.

Bool

Gleam bools are Erlang boolean atoms.

// Gleam
True
False
% Erlang
true
false

Int

Gleam ints are Erlang integers.

// Gleam
2001
% Erlang
2001

Float

Gleam floats are Erlang floats.

// Gleam
10.5
% Erlang
10.5

String

Gleam strings are Erlang UTF8 binary strings.

// Gleam
""
"Hello, Joe!"
"👷🏾‍♀️"
% Erlang
<<>>
<<"Hello, Joe!"/utf8>>
<<"👷🏾‍♀️"/utf8>>

Erlang's string() type is a list of integers that represent characters, and it is not compatible with Gleam strings. To convert Erlang character lists to Gleam strings use the unicode:characters_to_binary/1 Erlang function. The gleam/erlang/charlist module from the gleam_erlang package may be useful for working with Erlang character lists.

Nil

Gleam's Nil is the Erlang atom nil.

// Gleam
Nil
% Erlang
nil

Bit array

Gleam bit arrays are Erlang bit strings.

// Gleam
<<30, 56, 10>>
% Erlang
<<30, 56, 10>>

List

Gleam lists are Erlang lists.

// Gleam
[1, 2, 3]
% Erlang
[1, 2, 3]

All elements of a list must have the same type in Gleam.

In Gleam lists are always proper lists. Passing an Erlang improper list to Gleam code as a list is incorrect.

Tuples

Gleam tuples are Erlang tuples.

// Gleam
#(100, 1.9)
% Erlang
{100, 1.9}

Result

The Ok and Error variants of Gleam's result type are Erlang tagged tuples.

// Gleam
Ok(2)
Error(Nil)
% Erlang
{ok, 2}
{error, nil}

Occasionally Erlang will use just the atoms ok or error in place of a tagged tuple. These are not compatible with Gleam's result type.

Custom types

The variants of Gleam custom types are Erlang tagged tuples or Erlang atoms, depending on whether of not they have any fields.

The Gleam PascalCase variant names are written in snake_case in Erlang.

// Gleam
Guest
User(id: 10)
SuperUser(id: 11)
guest
{user, 123}
{super_user, 123}

The Gleam compiler will generate an Erlang header file for each custom type definition, containing an Erlang record definition for each variant. These can be included into Erlang modules if you would prefer to use the Erlang record syntax sugar.

Dict

The dict from Gleam's standard library is an Erlang map.

// Gleam
dict.new()
dict.from_list([#("a", 1), #("b", 2)])
% Erlang
#{}
#{<<"a"/utf8>> => 1, <<"b"/utf8>> => 2}

All keys of a dict and all values of a list must have the same types in Gleam.

Elixir externals

Using Elixir is similar to using Erlang.

The function names are written the same in the Elixir code. In Elixir modules have the implicit Elixir. prefix, so this must be specified when defining an Elixir external function. Any public function in any module can be used as an external.

defmodule Pokemon do
  def badge_count()
    8
  end
end

This is an Elixir module named Pokemon, with a public function called badge_count, which takes no arguments. The accompanying external function in Gleam would look like this:

@external(erlang, "Elixir.Pokemon", "badge_count")
pub fn pokemon_badge_count() -> Int

Note that even though the external function is written in Elixir the @external target is still erlang, as this is the target that the Gleam code is using.

Elixir macros are not usable outside of Elixir.

Gleam data in Elixir

Gleam data types are also common Elixir data types, making working with Gleam data in Erlang relatively straightforward.

Any Elixir type not listed below would be represented in Gleam using an external type.

Bool

Gleam bools are Elixir boolean atoms.

// Gleam
True
False
# Elixir
true
false

Int

Gleam ints are Elixir integers.

// Gleam
2001
# Elixir
2001

Float

Gleam floats are Elixir floats.

// Gleam
10.5
# Elixir
10.5

String

Gleam strings are Elixir strings.

// Gleam
""
"Hello, Joe!"
"👷🏾‍♀️"
# Elixir
""
"Hello, Joe!"
"👷🏾‍♀️"

Nil

Gleam's Nil is the Elixir atom nil.

// Gleam
Nil
# Elixir
nil

Bit array

Gleam bit arrays are Elixir bit strings.

// Gleam
<<30, 56, 10>>
# Elixir
<<30, 56, 10>>

List

Gleam lists are Elixir lists.

// Gleam
[1, 2, 3]
# Elixir
[1, 2, 3]

All elements of a list must have the same type in Gleam.

In Gleam lists are always proper lists. Passing an Elixir improper list to Gleam code as a list is incorrect.

Tuples

Gleam tuples are Elixir tuples.

// Gleam
#(100, 1.9)
# Elixir
{100, 1.9}

Result

The Ok and Error variants of Gleam's result type are Erlang tagged tuples.

// Gleam
Ok(2)
Error(Nil)
# Elixir
{:ok, 2}
{:error, nil}

Occasionally Elixir will use just the atoms ok or error in place of a tagged tuple. These are not compatible with Gleam's result type.

Custom types

The variants of Gleam custom types are Erlang tagged tuples or Erlang atoms, depending on whether of not they have any fields.

The Gleam PascalCase variant names are written in snake_case in Elixir.

// Gleam
Guest
User(id: 10)
SuperUser(id: 11)
:guest
{:user, 123}
{:super_user, 123}

The Gleam compiler will generate an Erlang header file for each custom type definition, containing an Erlang record definition for each variant. These can be used with with Elixir's Record module if you would like to use the macros it provides.

Dict

The dict from Gleam's standard library is an Elixir map.

// Gleam
dict.new()
dict.from_list([#("a", 1), #("b", 2)])
# Elixir
#{}
#{"a" => 1, "b" => 2}

All keys of a dict and all values of a list must have the same types in Gleam.

JavaScript externals

When defining JavaScript externals the module is internally used with a JavaScript import statement, so it typically should be a path to a JavaScript file, relative to location of the Gleam file that contains the external definition. The function name is written the same as in the JavaScript code, and any public function in any module can be used as an external.

// In src/my_app/pokemon.mjs
export function badge_count() {
  return 8;
}

This is a JavaScript module located at src/my_app/pokemon.mjs, with a public function called badge_count, which takes no arguments. The accompanying external function in the Gleam file src/my_app.gleam would look like this:

// In src/my_app/pokemon.gleam
@external(javascript, "./my_app/pokemon.js", "badge_count")
pub fn pokemon_badge_count() -> Int

Some JavaScript runtimes deviate from the specification and have different rules, so ensure that your paths are correct for the runtime you're using. For example, NodeJS may require a special .mjs file extension to be used for imports to work, which is why that extension is used in the previous example.

The NodeJS runtime also has special non-standard paths for importing code from "node modules", typically installed using npm. These paths can be used with external functions, but you will need to make sure that you have used a package.json file or the npm install command to install the desired node modules.

@external(javascript, "has-flag", "hasFlag")
pub fn argv_has_flag(name: String) -> Bool

Vendoring (i.e. copy/pasting the dependency code into your project) is strongly discouraged if you intend to publish your package to the Hex package manager. This is because it prevent dependency de-duplication resulting in excess code bloat, and most importantly it makes security audits and patches extremely difficult.

Gleam data in JavaScript

Since v1.13 Gleam code compiled to JavaScript will expose an API for constructing Gleam data types.

Any JavaScript type not listed below would be represented in Gleam using an external type.

Bool

Gleam bools are JavaScript booleans.

// Gleam
True
False
// JavaScript
true;
false;

Int

Gleam ints are JavaScript numbers, but the numbers must be whole numbers.

// Gleam
2001
// JavaScript
2001;

Float

Gleam floats are JavaScript numbers, without any restrictions.

// Gleam
10.5
// JavaScript
10.5;

Gleam doesn't have any syntax for infinity and NaN. You should avoid passing these to Gleam code.

String

Gleam strings are JavaScript strings.

// Gleam
""
"Hello, Joe!"
"👷🏾‍♀️"
// JavaScript
"";
"Hello, Joe!";
"👷🏾‍♀️";

Nil

Gleam's Nil is the JavaScript undefined value.

// Gleam
Nil
// JavaScript
undefined

Bit array

Gleam bit arrays are constructed using the BitArray$BitArray function from the Gleam prelude module, which takes a Uint8Array as an argument.

The Gleam prelude module can be imported as if it were located at src/gleam.mjs in any Gleam project. The Gleam build tool will internally provide this module.

// Gleam
<<30, 56, 10>>
// JavaScript, in src/wibble/wobble.mjs
import { BitArray$BitArray } from '../gleam.mjs';

BitArray$BitArray(new Uint8Array([30, 56, 10]));

List

Gleam lists arrays constructed using these functions from the Gleam prelude module:

// Gleam
[]
[1]
[1, 2, 3]
// JavaScript, in src/wibble/wobble.mjs
import { List$Empty, List$NonEmpty } from '../gleam.mjs';

List$Empty();
List$NonEmpty(1, List$Empty());
List$NonEmpty(1, List$NonEmpty(3, List$NonEmpty(3, List$Empty()));

All elements of a list must have the same type in Gleam. The second argument to the List$NonEmpty function must always be a list of the correct type.

These functions are provided for deconstructing lists:

// Gleam
case items {
  [] -> "Empty list"
  [e] -> "One element: " <> e
  [_, ..] -> "Multiple elements"
}
// JavaScript, in src/wibble/wobble.mjs
import {
  List$isEmpty,
  List$NonEmpty$first,
  List$NonEmpty$rest,
} from '../gleam.mjs';

if (List$isEmpty(items)) {
  return "Empty list";
} else {
  const items2 = List$NonEmpty$rest(items)
  if (List$isEmpty(items2)) {
	const e = List$NonEmpty$first(items);
    return "One element: " + e;
  } else {
    return "Multiple elements";
  }
}

Working with Gleam lists in JavaScript is not as nice as in Gleam, so you may instead decide to use the Array type from the gleam/javascript/array module from the gleam_javascript package and then convert the arrays into lists in Gleam code.

Tuples

Gleam tuples are JavaScript arrays.

// Gleam
#(100, 1.9)
// JavaScript
[100, 1.9];

Gleam tuples are immutable, so you should never mutate an array that is being used as a Gleam tuple. For example, do not use the items.push(x) method or the items[i] = x assignment syntax.

Result

Gleam results are constructed using these functions from the Gleam prelude module:

// Gleam
Ok(2)
Error("oh no!")
// JavaScript, in src/wibble/wobble.mjs
import { Result$Ok, Result$Error } from '../gleam.mjs';

Result$Ok(2);
Result$Error("oh no!");

These functions are provided for deconstructing results:

All values passed to Result$ functions must be Gleam results. All values passed to Result$Ok$0 must be Ok results. All values passed to Result$Error$0 must be Error results.

Custom types

Any custom types defined in Gleam will have JavaScript functions generated for them, similar to those for the Gleam result type. These functions can be imported using a path to the Gleam file that defines the custom type, though with the extension .mjs instead of .gleam. You should not create a file at this location in your project, it is internally provided by the Gleam build tool.

When importing a module from another package specify a path as if the other package's src directory is located at the root of your project, but with the name of the package as the directory name instead of src. For example, if you wish to use the gleam/option module from the gleam_stdlib package, and the JavaScript file you are importing it from it located at src/wibble/wobble.js, then the import path would be ../../gleam_stdlib/gleam/option.mjs.

This SchoolPerson custom type will be used to demonstrate the JavaScript API provided for custom types.

// Gleam, in src/school.gleam
pub type SchoolPerson {
  Teacher(name: String)
  Student(name: String, year: Int)
}

For each variant of the custom type a TypeName$VariantName function is provided to construct that variant. They will take the same arguments that the variant constructors would take in Gleam code.

// Gleam
Teacher("Mr Schofield")
Student("Adam", 7)
// JavaScript, in src/code.mjs
import { SchoolPerson$Teacher, SchoolPerson$Student } from '../school.mjs';

SchoolPerson$Teacher("Mr Schofield");
SchoolPerson$Student("Adam", 7);

For each variant of the custom type a TypeName$isVariantName function is provided to determine which variant a value is. The value passed to this function must be the correct type.

// JavaScript, in src/code.mjs
import {
  SchoolPerson$Teacher,
  SchoolPerson$isTeacher,
  SchoolPerson$isStudent,
} from '../school.mjs';

const teacher = SchoolPerson$Teacher("Mr Schofield")

SchoolPerson$isTeacher(teacher);
// -> true

SchoolPerson$isStudent(teacher);
// -> false

For each field of each variant of the custom type a TypeName$VariantName$index function is provided to positionally access these fields. The value given to these functions must be the correct variant.

// JavaScript, in src/code.mjs
import {
  SchoolPerson$Student,
  SchoolPerson$Student$0,
  SchoolPerson$Student$1,
} from '../school.mjs';

const student = SchoolPerson$Student("Koushiar", 7);

SchoolPerson$Student$0(student);
// -> "Koushiar"

SchoolPerson$Student$1(student);
// -> 7

If any fields have been given labels then similar functions are also provided using the labels names instead of the field indexes. The value given to these functions must be the correct variant.

// JavaScript, in src/code.mjs
import {
  SchoolPerson$Student,
  SchoolPerson$Student$name,
  SchoolPerson$Student$year,
} from '../school.mjs';

const student = SchoolPerson$Student("Koushiar", 7);

SchoolPerson$Student$name(student);
// -> "Koushiar"

SchoolPerson$Student$year(student);
// -> 7

If any fields in the same position have the same label across all variants then a TypeName$label_name function is provided to access them. The value given to these functions must be the correct type but can be any variant.

// JavaScript, in src/code.mjs
import {
  SchoolPerson$Student,
  SchoolPerson$Teacher,
  SchoolPerson$name,
} from '../school.mjs';

const teacher = SchoolPerson$Teacher("Mr Schofield");
const student = SchoolPerson$Student("Koushiar", 7);

SchoolPerson$name(teacher);
// -> "Mr Schofield"

SchoolPerson$name(student);
// -> "Koushiar"

Dict

Types that have no literal syntax in Gleam, such as the dict type, don't have any special JavaScript API. For these types the regular Gleam function can be imported and used.

// Gleam
dict.from_list([
  #("Lucy", 2),
  #("Ada", 1),
])
// JavaScript, in src/code.mjs
import { from_list as list_to_dict }
  from '../gleam_stdlib/gleam/dict.mjs';
import { to_list as array_to_list }
  from '../gleam_stdlib/gleam/javascript/array.mjs';

list_to_dict(array_to_list([
  ["Lucy", 2],
  ["Ada", 1],
]));