PureScript mini-tutorial using Nix

EDIT (Mar, 2023): Checkout https://github.com/purifix/purifix if you want to Nixify your PureScript projects.

My Haskell app Neuron recently received a contribution that added support for the very useful client-side search feature. JavaScript was used to implement it; however after having gotten used to merrily creating Reflex-FRP-based web apps (frontend and backend both in Haskell), writing raw JavaScript had very little appeal to me, which left me with the following options:

  • GHCJS: Reflex
  • GHCJS: Miso
  • PureScript

One appeal of PureScript is that it is comparitively lightweight to install, develop and use (which is how I’d describe neuron itself).

Creating a Hello World project

If you already use Nix (or NixOS), getting a quick feel for PureScript is just a matter of running:

nix-shell -p purescript -p spago

Spago is the PureScript package manager. It uses Dhall for configuration, which is exactly what Neuron uses as well. Once you are in the nix-shell we shall create a new PureScript project:

mkdir /tmp/app1 && cd /tmp/app1
spago init

The project will have minimal files:

$ tree
.
β”œβ”€β”€ packages.dhall
β”œβ”€β”€ spago.dhall
β”œβ”€β”€ src
β”‚Β Β  └── Main.purs
└── test
    └── Main.purs

2 directories, 4 files

and very basic PureScript code:

-- src/Main.purs
module Main where

import Prelude

import Effect (Effect)
import Effect.Console (log)

main :: Effect Unit
main = do
  log "🍝"

To build the final JavaScript β€œexecutable”, run spago bundle-app; it will generate a index.js for use from HTML.

$ spago bundle-app
[info] Installation complete.
[info] Build succeeded.
[info] Bundle succeeded and output file to index.js

$ wc -l -c index.js
 29 792 index.js

That’s basically it. Very easy to install and use!

ghcid of PureScript

Not using any fancy IDE, I find ghcid to be critical to my Haskell development workflow; its fast compile-reload cycle facilitates a very delightful development experience. I wanted to have this with PureScript. Fortunately, such a tool exists β€” pscid. pscid is available in Nix, so you may simply restart that nix-shell as:

nix-shell -p purescript -p spago -p pscid

Then visit the project directory, and run:

pscid

Now modify src/Main.hs and watch it recompile on the fly. When you are done with your changes, you would run spago bundle-app at the end to build the final JavaScript.

β€œSo how do I use getElementById?”

Haskellers are familiar with Hoogle. In PureScript ecosystem, that is called Pursuit. I wanted to know, as part of adding PureScript to neuron, how to use the famous getElementById function in PureScript; and pursuit came to help.

getElementById :: String -> NonElementParentNode -> Effect (Maybe Element)

Hmm, what is β€œNonElementParentNode”? This function is from the purescript-web-dom library, which uses the DOM spec which does indeed document this second argument, although it is implicitly provided by this in JavaScript. In PureScript you would pass it explicitly. HTML Document is one of the values it can take:

main = do
  doc <- map toNonElementParentNode $ document =<< window
  myElem <- getElementById "someId" doc

EDIT: The lack of documentation around things like this is something I noticed immediately in the PureScript ecosystem; a lobste.rs user explains it here.

Logging to console

The project template already uses the log function to log strings to browser console. But how do we log an arbitrary PureScript object, like the DOM element? Using traceM:

import Debug.Trace (traceM)
main = do
  doc <- map toNonElementParentNode $ document =<< window
  myElem <- getElementById "someId" doc
  traceM myElem

And that’s all it takes to get the initial feel for what it is like to develop PureScript as a Haskeller who comes from the world of Nix and GHCJS.

#blog #nojs