DEV Community

Cover image for purerl - Integrating PureScript into Elixir projects
Rickard Andersson for Quanterall

Posted on • Updated on

purerl - Integrating PureScript into Elixir projects

What is PureScript?

PureScript 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 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 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 for turning PureScript 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.

$ mix new purerl_up_and_running
...
Enter fullscreen mode Exit fullscreen mode

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, a general version management tool we can use for all the tools we'll need in this project.

asdf

$ 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
Enter fullscreen mode Exit fullscreen mode

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:

$ spago init
...
Enter fullscreen mode Exit fullscreen mode

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 --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
Enter fullscreen mode Exit fullscreen mode

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 --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"
 }
Enter fullscreen mode Exit fullscreen mode

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 --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"}
     ]
Enter fullscreen mode Exit fullscreen mode

The package we are using is called 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:

module OurModule where

hello :: String
hello = "Hello!"
Enter fullscreen mode Exit fullscreen mode

iex

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

iex(1)> :ourModule@ps.hello()
"Hello!"
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
bibzmibz profile image
bibzmibz

thanks for this article