Purescript for Web Development

Getting Started with Purescript for Web Development

Update: Go buy the Purescript Book: https://leanpub.com/purescript by the author of the language.

There’s good documentation around the Purescript language, but none of the tutorials I’ve seen take you from nothing to running something in the browser. This post will hopefully fill that gap.

If you don’t feel like reading, you can clone the code for this post and just follow the directions in the README and start playing around.

This guide assumes you already know why Purescript is awesome and have installed the compiler. If you don’t, check out http://purescript.readthedocs.org/en/latest/intro.html and http://purescript.readthedocs.org/en/latest/start.html

You’ll need to install grunt, grunt-cli and bower globally. Grunt to manage your build, bower to manage dependencies.

npm install -g grunt grunt-cli bower

Create a directory for your project and cd into it. You’ll then need to install grunt locally into the directory of this project, as well as the grunt-purescript and grunt-contrib-watch plugins. The purescript plugin provides tasks for running the Purescript compiler, while grunt-contrib-watch will allow is to run grunt watch and rebuild our project whenever we save a Purescript (.purs) file.

npm install grunt grunt-purescript grunt-contrib-watch

You’ll then want to create Gruntfile.js with the following contents:

module.exports = function(grunt) {
  "use strict";

  grunt.initConfig({

    psc: {
      options: {
        main: "Example.Main",
        modules: ["Example.Main"]
      },
      all: {
        src: [ "src/**/*.purs"
             , "bower_components/**/src/**/*.purs"],
        dest: "dist/example.js"
      }
    },
    watch: {
      files: [ "src/**/*.purs"
             , "bower_components/**/src/**/*.purs"],
      tasks: ["psc:all"]
    }
  });

  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks("grunt-purescript");

  grunt.registerTask("default", ["psc:all"]);
};

This sets up a default task that will build all .purs files in your src and bower_components directories. It will output a js file that invokes the function named “main” within the Example.Main module. The strangely named “modules” option is for dead code elimination. The compiler will remove any code not referenced by the listed modules from the compiled javascript.

Now create a “src” directory and an “Example” directory within it, and finally a file called Main.purs within the example directory. Main.purs should look like this:

module Example.Main where

import Debug.Trace

main = trace "hello purescript"

You should now be able to run grunt and have this compile successfully. You can also run grunt watch to automatically rebuild the project every time you save a .purs file.

Now just drop the newly created dist/example.js in an index.html file and open it in a browser.

<html>
<head>
  <title>Example purescript app</title>
</head>
<body>
  <p> Check the console </p>
  <script type="text/javascript" src="dist/example.js"></script>
</body>
</html>

You should be able to open your browser’s developer tools and see “hello purescript” in the console.

Using 3rd party libraries

In a real project you’ll inevitably need to venture outside the standard library. The Purescript community currently manages dependencies with bower. To get started with bower, run bower init and answer the prompts. This will create a bower.json file for your project.

To add dependences to your project, add a dependencies section to generated JSON and list any dependencies. We’ll use purescript-random to generate random numbers as an example. “*” in this case indicates we want to use the latest version, you can also specify an exact version number.

{
  "name": "purescript-web-dev-example",
  "version": "0.0.0",
  "authors": [
    "Curtis Gagliardi <curtis@curtis.io>"
  ],
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "purescript-random": "*"
  }
}

You can find more libraries by searching bower for “purescript”: http://bower.io/search/.

Now that we’ve specified a dependency, run bower install install to install it. Bower will insert it into the “bower_components” directory we mentioned in our Gruntfile.

We can now use the library in our Main module:

module Example.Main where

import Debug.Trace
import Control.Monad.Eff.Random

main = do
  rand <- random
  trace $ "hello purescript: " ++ show rand

Run grunt again and refresh the page. You should see something like: “hello purescript: 0.7828595286700875” in the console.

Using the FFI

Debug.Trace lets you write to the console, but what if we want to alert something?

There’s no alert function in the standard library, but Purescript’s FFI makes it easy to wrap raw javascript functions.

Since alert is impure, it will live in the Eff monad and we’ll need a type for the effect. We can define a foreign data type like so:

foreign import data Alert :: !

! is the kind of effects in Purescript.

We’ll also need to import Control.Monad.Eff in order to use the Eff type.

import Control.Monad.Eff

We can then define our alert function with inline javascript using foreign import. Since effects are represented as functions of no arguments, our alert function returns a function of no arguments that will invoke alert when called.

foreign import alert
  "function alert(s) { \
  \  return function() { \
  \    window.alert(s); \
  \  }; \
  \};" :: forall r. String -> Eff (alert :: Alert | r) Unit

Our type signature declares that it takes a String and returns an alert effect that produces no meaningful return value and thus returns Unit.

We can now use alert in our main function similar to the way we use trace:

module Example.Main where

import Debug.Trace
import Control.Monad.Eff
import Control.Monad.Eff.Random

foreign import data Alert :: !

foreign import alert
  "function alert(s) { \
  \  return function() { \
  \    window.alert(s); \
  \  }; \
  \};" :: forall r. String -> Eff (alert :: Alert | r) Unit

main = do
  rand <- random
  alert "Woooo!"
  trace $ "hello purescript: " ++ show rand

If you run grunt again and refresh you should see an alert box pop up.

This is a minimal example, the FFI and Eff monad are well documented in more depth on the Purescript blog:

Resources

Gotchas

Hopefully this is enough to get yout started building a real web app with Purescript. If you have any questions, comments, corrections or complaints, please email or tweet at me.