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
...
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
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
...
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
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"
}
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"}
]
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!"
iex
Let's run iex -S mix
in the project folder and execute the following:
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.
Top comments (1)
thanks for this article