purerl - Integrating PureScript into Elixir projects
Tags: purescript, beam, elixir, erlang
Published: 2023-01-11 09:35:41

---
title: purerl - Integrating PureScript into Elixir projects
published: true
description: A short intro to PureScript in Elixir projects
tags: purescript, beam, elixir, erlang 
cover_image: https://miro.medium.com/max/720/1*j9dqogFLMLW8Hrm6eofFkw.webp
series: ["purerl - PureScript + Erlang/Elixir"]
# published_at: 2022-12-11 12:29 +0000
---

## What is PureScript?

[PureScript](https://www.purescript.org) is a Haskell-like language aimed at providing an alternative to TypeScript for statically typed programming in the JavaScript space. I highly recommend taking a look at PureScript for your compile-to-JavaScript needs outside of the use case we'll be talking about in this post.

PureScript is most easily [compared to TypeScript](https://www.youtube.com/watch?v=JTEfpNtEoSA) where it could be summed up as having many of the defaults you'd want in most TypeScript projects anyway, then layered with effects being noted in the types of functions on top.

## What is Erlang?

[Erlang](https://www.erlang.org/) is a language developed from the 80's onwards for the purposes of writing highly concurrent software. The primary purpose from the beginning was telephony systems where fault tolerance, concurrency and failure isolation were important concepts. These happen to be very important concepts in essentially all networked services nowadays.

## What is Elixir?

Elixir is a collection of libraries, a syntax and a slim set of language features that enable a somewhat nicer workflow over Erlang. The libraries come into play when we want to use already created functionality in our PureScript code, as it might not be available in Erlang.

## What is `purerl`?

[`purerl` is a compiler](https://github.com/purerl/purerl) for turning [PureScript](https://www.purescript.org/) code into Erlang code, so that you're able to write BEAM (the Erlang virtual machine) applications using it.

This enables us to use it together with both Elixir and Erlang, which in a beautiful symbiosis extends the power of all three languages.

## Our project setup

We'll start with an Elixir application that we'll add automatic compilation of `*.purs` files to, which will provide the perfect basis for getting things up and running quickly, easily and with a robust foundation to build on.

### `mix`

In this particular example we'll use `mix new`, but `mix phx.new` will work exactly the same way and it's likely that any other `new` template would work as well. We're using `mix` because it's the standard tool for managing Elixir projects.

```bash
$ mix new purerl_up_and_running
...
```

When our project has been created we want to make sure that the versions we have of everything are clearly described. For this purpose we'll use [`asdf`](https://asdf-vm.com/), a general version management tool we can use for all the tools we'll need in this project.

### `asdf`

```bash
$ asdf plugin add erlang && asdf install erlang 25.1.2 && asdf local erlang 25.1.2
...
$ asdf plugin add elixir && asdf install elixir 1.14.2-otp-25 && asdf local elixir 1.14.2-otp-25
...
$ asdf plugin add purescript && asdf install purescript 0.15.3 && asdf local purescript 0.15.3
...
$ asdf plugin add spago && asdf install spago 0.20.9 && asdf local spago 0.20.9
...
$ asdf plugin add purerl && asdf install purerl 0.0.17 && asdf local purerl 0.0.17
...
$ asdf plugin add rebar && asdf install rebar 3.20.0 && asdf local rebar 3.20.0
```

The process for each tool here is to install the `asdf` plugin, a version of the tool and then set that version as the locally used one for our project. This ensures that other contributors can use the correct versions.

### PureScript bits via `spago`

#### Initial setup

First we run `spago init` in our project folder:

```bash
$ spago init
...
```

This sets up our project to have basic PureScript files. The source directory for PureScript is `src`, so out of the box we are able to keep it separate from our Elixir code, which lives in `lib`.

#### Minor changes to our project files

We'll have to modify our `spago.dhall` and `packages.dhall` files just a bit in order for our project to point to the correct `purerl` things:

##### `packages.dhall`

```diff
diff --git a/packages.dhall b/packages.dhall
index e13009d..28bcb0c 100644
--- a/packages.dhall
+++ b/packages.dhall
@@ -99,7 +99,6 @@ in  upstream
 -------------------------------
 -}
 let upstream =
-      https://github.com/purescript/package-sets/releases/download/psc-0.15.3-20220712/packages.dhall
-        sha256:ffc496e19c93f211b990f52e63e8c16f31273d4369dbae37c7cf6ea852d4442f
+      https://github.com/purerl/package-sets/releases/download/erl-0.15.3-20220629/packages.dhall
 in  upstream
```

The change we're making here is that we are pointing the package set, the set of packages known to work together for this PureScript compiler version, to the `purerl` package set, i.e. a package set that uses Erlang code as the implementation language.

##### `spago.dhall`

The definition of our PureScript side of the project is done in `spago.dhall` and we'll add just one minor bit here:

```diff
diff --git a/spago.dhall b/spago.dhall
index 41a8576..a9b223a 100644
--- a/spago.dhall
+++ b/spago.dhall
@@ -14,4 +14,5 @@ to generate this file without the comments in this block.
 , dependencies = [ "console", "effect", "prelude" ]
 , packages = ./packages.dhall
 , sources = [ "src/**/*.purs", "test/**/*.purs" ]
+, backend = "purerl"
 }
```

The change above tells PureScript that we want to use a special compiler backend, i.e. `purerl`, in order to compile our PureScript code.

### Elixir code needs to understand our project

In order to compile our PureScript code we'll rely on Elixir managing the compilation process as part of its normal compilation, via `mix` compilers.

#### `mix.exs`

```diff
diff --git a/mix.exs b/mix.exs
index 6ba9745..657162c 100644
--- a/mix.exs
+++ b/mix.exs
@@ -7,7 +7,9 @@ defmodule PurerlUpAndRunning.MixProject do
       version: "0.1.0",
       elixir: "~> 1.14",
       start_permanent: Mix.env() == :prod,
-      deps: deps()
+      deps: deps(),
+      compilers: [:purerl] ++ Mix.compilers(),
+      erlc_paths: ["output"]
     ]
   end

@@ -21,6 +23,7 @@ defmodule PurerlUpAndRunning.MixProject do
   # Run "mix help deps" to learn about dependencies.
   defp deps do
     [
+      {:purerlex, "~> 0.4.2"}
       # {:dep_from_hexpm, "~> 0.3.0"},
       # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
     ]
```

The package we are using is called [`purerlex`](https://hex.pm/packages/purerlex) and works out of the box as long as we also add that Erlang source files can be found in the `output` folder. We need to run `mix deps.get` in our project folder to get this dependency as well.

## Adding PureScript code

We are now ready to add PureScript code that can be executed on the BEAM. We'll try this functionality out in `iex`, the interactive Elixir session that we can use to evaluate expressions.

### `OurModule.purs`

Let's add a file in `src/OurModule.purs` containing the following:

```haskell
module OurModule where

hello :: String
hello = "Hello!"
```

### `iex`

Let's run `iex -S mix` in the project folder and execute the following:

```elixir
iex(1)> :ourModule@ps.hello()
"Hello!"
```

Note here that our module name is being translated in a particular way. First of all it's converted into camel-case from the PascalCase standard that PureScript modules use. On top of that modules are also compiled with a `@ps` suffix that is very close to the `Elixir.` prefix that Elixir modules are compiled with, to distinguish the modules from modules generated with other languages.

## Where to go from here

We're now able to compile and run PureScript code so that we can use it in the rest of our Elixir code. This means we can write entire subsystems in PureScript instead and start them in our application supervisor, meaning they are started from the root of our application. If we have a subsystem that is best expressed in a statically typed language this is now something we can cover as a use case.

In the next article we'll go over how to write one of these subsystems so that we can take advantage of this newfound ability to compile and run PureScript code in our project.