PureScript for the Haskeller

Where to get started in PureScript for the Haskell programmer

Posted by Dennis Gosnell

This article is an introduction to PureScript for someone who knows Haskell (but may not be familiar with JavaScript). PureScript is very similar to Haskell. If you know Haskell, PureScript will be very easy to learn.

This article gives an overview of the differences between PureScript and Haskell, and then dives into things specific to PureScript like build systems and DOM manipulation libraries.

Differences Between Haskell and PureScript

There are only a few key differences between Haskell and PureScript. Here is a comprehensive list. You should read this first.

Here are a couple things that often came up when I was writing code:

  • forall is explicit in PureScript. This is somewhat annoying, but easy to get used to. In some future version of PureScript, it might not be required.
  • Instances are named.
  • Orphan instances are completely disallowed.
  • No functional dependencies or type families. This makes type checking in PureScript worse than Haskell in some cases.
  • Nothing like Template Haskell. This makes using lenses and JSON more time-consuming.
  • There is no special syntax for tuples.
  • The function composition operator is not (.), but (<<<).
  • undefined is not provided in the Prelude, but it can be emulated.
  • PureScript’s Prelude is somewhat different from Haskell’s.
    • It has to be imported in every module that uses it.
    • It doesn’t export things like Maybe and Either. They live in separate modules.
    • The typeclass hierarchy is much more fine-grained (see below).
    • Alternative Preludes that export many modules are more common in Purescript. See the purescript-batteries package, for example.
  • PureScript uses extensible records (see below).
  • PureScript doesn’t use a IO monad, but instead extensible effects with the Eff Monad (see below).

Typeclass Hierarchy

The typeclass hierarchy provided by PureScript’s Prelude is much more fine-grained than Haskell. It is worth reading through PureScript’s Prelude to see how it is split up. It is not very long.

A lot of classes and types provided by Haskell’s base package are separate packages in PureScript. This includes Maybe, Either, Foldable, Traversable, Identity, List, Monoid, etc. When looking for which package contains a class or type you are used to from Haskell, you may want to use Pursuit (see below).

There is also a list of recommended PureScript libraries on the PureScript wiki. This is a good resource to use to find common packages.

Extensible Records

Records in PureScript don’t work like records in Haskell. PureScript records are introduced nicely in Chapter 3 of the PureScript book.

PureScript has extensible records (also called “row polymorphism”). A short introduction is given in Chapter 5.7 of the PureScript book. Make sure you understand extensible records before reading about the Eff monad.

One neat thing about PureScript records is that they can be used in newtypes. The following code in Haskell will fail to compile:

newtype Foo = Foo { a :: String, b :: Int }

You’ll get the following error message:

foo.hs:1:15:
    The constructor of a newtype must have exactly one field
      but ‘Foo’ has two
    In the definition of data constructor ‘Foo’
    In the newtype declaration for ‘Foo’

However, the same code in PureScript is completely fine!

newtype Foo = Foo { a :: String, b :: Int }

When looking at the type of the Foo constructor in psci (interactive environment like ghci for PureScript, see below), we can see that it takes a record as an argument:

> :t Foo
{ a :: String, b :: Int } -> Foo

Eff Monad

PureScript doesn’t use the IO monad. It uses the Eff monad. There are two good tutorials on using the Eff monad:

Since callbacks are used so heavily in JavaScript, PureScript code frequently uses the Aff monad. The Aff monad is an asynchronous effect monad. It may be worthwhile to check it out.

FFI and JavaScript

You can get pretty far in PureScript without ever needing to drop down to JavaScript. But calling out to JavaScript is sometimes necessary. Luckily, PureScript has a very nice JavaScript FFI. Here are two good tutorials on PureScript’s FFI:

Pursuit

Pursuit is like a combination of Hackage and Hoogle/Hayoo. It should be your first stop when looking for a function/type/class that you are used to from Haskell. Things in PureScript are often named the same as their counterparts in Haskell.

Compiler and Build Tools

The PureScript/JavaScript ecosystem is much more complex than the Haskell ecosystem. In Haskell, when building a project, the normal advice is to “just use stack”.1 In PureScript, you have to deal with node, npm, bower, pulp, and gulp.2 It may be second-nature for someone familiar with JavaScript, but it can be intimidating to a Haskeller.

The following sections explain the relationship between Node.js, npm, Bower, Pulp, Gulp, and Grunt.

I have created two example projects that you can use to play around with the following tools. One project is using Node.js, npm, Bower, and Pulp, and the other project is using Node.js, npm, Bower, and Gulp. You can use either project to follow long.

Node.js

Node.js is a runtime environment for developing server-side web applications. It lets you run JavaScript code on a server, instead of just in a browser.

Node.js has to be installed to be able to use PureScript. It is recommended to install Node.js with your distribution’s package manager.

npm

npm is a package manager for JavaScript and Node.js. You can find the package repository here.

npm is used to install other build tools like bower and gulp (discussed below). It is recommended to install npm with your distribution’s package manager.

The command npm install is used to install packages from the npm package repository. It creates a directory node_modules/ in your current working directory with the installed packages underneath it.

For example, if you run npm install bower to install the bower package, the bower executable will be created as node_modules/bower/bin/bower.

All executables installed with npm will be available as symbolic links in node_modules/.bin/. You may want to add this directory to your PATH, or use one of the other techniques specified in this stackoverflow answer.

npm can also install packages globally. For instance, you can use the command npm install -g bower to install bower globally. By default, global packages are installed to /usr/lib/node_modules/.

One easy way to use npm is to use a package.json file. The package.json file lists the dependencies for the current project. All dependencies will be installed if you run npm install with no arguments.

Here is an example package.json file that lists the PureScript compiler, Bower, and gulp as dev-time dependencies, and the virtual-dom package as a runtime dependency:

{
  "name": "purescript-foo",
  "version": "1.0.0",
  "description": "example package",
  "author": "Pyour Skrept <[email protected]>",
  "license": "Apache 2.0",
  "homepage": "https://github.com/example/purescript-foo",
  "dependencies": {
    "virtual-dom": "^2.1.1"
  },
  "devDependencies": {
    "purescript": "^0.7.6",
    "bower": "^1.6.5",
    "gulp": "^3.9.0",
    "gulp-purescript": "^0.7.0"
  },
  "repository": {
    "type": "git",
    "url": "git+ssh://[email protected]/example/purescript-foo.git"
  }
}

Check out the package.json file in the example project. Use npm install to install all the tools specified in the package.json file.

PureScript Compiler

The PureScript compiler provides the executables psc and psci. These correspond to ghc and ghci, respectively.

The PureScript compiler can be installed multiple ways. Since we are using npm to install bower, gulp, etc., the easiest way is just to install it with npm:

$ npm install purescript

You can access psc and psci in node_modules/.bin/.

Bower

Bower is a separate package manager from npm. npm is often used for managing Node.js modules (for example, build tools, the PureScript compiler, gulp, etc). Bower is used to manage packages related to the front-end, like PureScript libraries. Here is an answer on stackoverflow explaining the difference between npm and Bower.

Here is Bower’s package repository. If you search for “purescript”, you should find many packages. PureScript packages are almost all uploaded to Bower. Pursuit even requires that new packages are uploaded to Bower’s repository.

Bower can be installed with npm:

$ npm install bower

You can access bower in node_modules/.bin/.

Once you have installed Bower, you can use Bower to install PureScript packages. This is how you would install the purescript-prelude package:

$ bower install purescript-prelude

bower installs packages to the bower_components/ directory in the current working directory. After installing purescript-prelude you should be able to find PureScript source files under bower_components/purescript-prelude/.

Just like npm can use a package.json file, Bower can use a bower.json file to specify multiple packages. Here is an example bower.json file. It specifies that we are using the purescript-prelude and purescript-console libraries in the current project:

{
  "name": "purescript-foo",
  "version": "1.0.0",
  "moduleType": [ "node" ],
  "ignore": [ "**/.*", "node_modules", "bower_components", "output" ],
  "dependencies": {
    "purescript-console": "^0.1.0",
    "purescript-prelude": "^0.1.0"
  }
}

To install all the files listed in bower.json, the install command is used:

$ bower install

In order to update libraries to newer versions, the update command is used:

$ bower update

Check out the bower.json file in the example projects. Try running bower install and bower update and see what happens.

Some people in the JavaScript community are “anti-Bower”, but it is still heavily used in the PureScript community.3

Pulp

Pulp is a build tool specific to PureScript. It is similar to stack or cabal for Haskell. It works well for libraries and simple projects, but it does not have enough flexibility for larger projects. It seems like the general recommendation from the PureScript community is to use Pulp for simple libraries, and gulp for any non-trivial application.4

Pulp can be installed with npm:

$ npm install pulp

You can access pulp in node_modules/.bin/.

Pulp can be used to build and test your project:

$ pulp build
...
$ pulp test
...

Pulp can also act as a frontend for Bower. This is how we update library dependencies with Bower:

$ bower update

This uses Pulp to do the exact same thing. It uses Bower behind the scenes:

$ pulp dep update

You can find other functions in the Pulp README.

Try running pulp build and pulp test in the example project for Pulp. Try running pulp server and opening up the index.html file in your browser.

Gulp

Gulp is a full-featured, dependency-tracking build tool. It is similar to Make or Shake.

Gulp is used instead of Pulp in a couple different cases. The most common is when building a project that produces multiple JavaScript files as output. It is also used when you need to change build settings that Pulp does not provide access to. It is also helpful when you are adding PureScript code to an existing project or workflow.

It seems like people using PureScript in production (SlamData) use Gulp almost exclusively.

Gulp can be installed with npm:

$ npm install gulp

You can access gulp in node_modules/.bin/.

Just like make uses a Makefile, gulp uses a gulpfile.js. This file lists the different targets available, and what should happen when they are run.

Take a look at the gulpfile.js from the example project. Try running the build and test targets:

$ gulp build
...
$ gulp test
...

Try running the server target and opening up site/index.html in a browser.

$ gulp server
...

Grunt

Grunt is a build tool similar to Gulp. At one point, Grunt was the common build tool for PureScript, but it has since given way to Pulp and Gulp. Grunt is no longer used to build PureScript code.

If you are interested, here is an article describing the differences between Grunt and Gulp.

DOM Manipulation Libraries

PureScript is mostly used for writing frontend code. Because of this, there are many DOM manipulation libraries available. In this section, I talk about five different PureScript libraries used for DOM manipulation: purescript-simple-dom, purescript-dom, purescript-react, Thermite, and Halogen. They are ordered from least-used to most-used. When using PureScript in production, you should really consider using Thermite or Halogen.

purescript-simple-dom

Like the name implies, purescript-simple-dom is the simplest of the four libraries. It provides an interface similar to that provided by web browsers. It is easy to create new nodes in an HTML Document, append child nodes to an element, etc. Code written using purescript-simple-dom is very similar to code written using raw JavaScript (although PureScript makes it more type-safe).

A small amount of sample code is available in the project’s README. It does not have any type information, so it is somewhat hard to read. However, the library is very simple so this shouldn’t be much of a problem.

The purescript-simple-dom, repository hasn’t been very active over the past month. Two (minor) issues I have created in the past month did not get a response.

purescript-simple-dom should be easy to use even for someone just getting started with languages like PureScript or Haskell.

purescript-dom

The purescript-dom library is similar to purescript-simple-dom. It provides a low-level interface for working with the browser DOM. It was created by Gary Burgess because of sporadic activity and the opinionated design of purescript-simple-dom. It is intended to be the low-level base for all future libraries that provide interfaces to the DOM. Ideally, purescript-simple-dom could be implemented as a wrapper around purescript-dom, but it hasn’t happened yet. purescript-dom is used heavily in Halogen.

There is no sample code or examples for purescript-dom. However, it is a straight wrapper around the DOM, so it shouldn’t be too hard to figure out.

Because it is used in Halogen, purescript-dom gets regular updates.

purescript-dom can be used in all places purescript-simple-dom can be used. It is the recommended library to use if you are working with the browser’s DOM and don’t want to pull in a big library like Thermite or Halogen.

purescript-react

purescript-react is a set of low-level bindings to React for PureScript. React is a library that abstracts updating a UI. From the Why React? page:

Many people choose to think of React as the V in MVC. We built React to solve one problem: building large applications with data that changes over time. Simply express how your app should look at any given point in time, and React will automatically manage all UI updates when your underlying data changes.

Examples for purescript-react can be found both in the README and in the tests.

I have not personally used purescript-react, but it looks like there has been development within the last month. The author, paf31, seems to respond quickly to issues and pull requests.

purescript-react will appeal to people coming from JavaScript with knowledge of React. Like simple-dom, it gives an easy way to make use of your existing knowledge in a type-safe fashion.

Thermite

From the Thermite README:

purescript-thermite is a simple PureScript wrapper for purescript-react. It does not (and does not aim to) provide all of the functionality of ReactJS, but instead to provide a clean API to the most commonly-used parts of its API.

Examples for Thermite can be found in the README, as well as the test project. The running test project can be viewed here.

I have not personally used Thermite, but it looks like there has been some acitivity within the last month. The author, paf31, responds to issuse and pull requests quickly.

Halogen

Halogen is quite possibly the most interesting of the DOM manipulation libraries for PureScript. It is similar to Thermite but aims to be more type-safe and composable. It is based on virtual-dom (a library similar to, but more low-level, than React). It feels like a well-typed, easy-to-use version of Elm.

Halogen makes use of many advanced concepts like free monads, functor coproducts, etc. For an intermediate/advanced Haskeller, it provides very nice abstractions for developing web frontends.

Halogen has a README that explains in detail how to use the library. There are also 10+ example projects in the examples directory.

Halogen is being used heavily by SlamData. Because of this, its development seems to be progressing faster than any of the other libraries. The developers of Halogen are very quick in responding to issues and pull requests. This makes working with Halogen a delight.

PureScript in Industry

In this section I talk about companies using PureScript in industry and users you are likely to see active on Github.

Companies

There are not currently many companies using PureScript in production. SlamData is a notable exception.

If you know any other companies that should be added to this list, please let me know.

SlamData

SlamData is providing an open source, visualization solution for NoSQL data. PureScript is being used in the frontend for visualization of data. SlamData is currently employing four full-time PureScript developers.

People

The people below are all very active on Github in a variety of PureScript projects. You’ll likely run in to them if you submit issues or send pull requests.

If you know of anyone that should be added to this list, please let me know.

Phil Freeman (paf31)

Phil is the creator of PureScript. He contributes a lot of code to the PureScript compiler, as well as responding to issues and pull requests. He also maintains the purescript-react and Thermite libraries.

Gary Burgess (garyb)

Gary is working for SlamData and is also very active in the PureScript community. He is a coauthor of the PureScript compiler and maintains the Halogen project. Gary is very helpful on Github. He has responded to many of my issues (even silly ones). If PureScript succeeds as a language, it will be partially due to the helpfulness of Gary.

John A. De Goes (jdegoes)

John is the CTO of SlamData. He is active within the Halogen project and also responds to many issues in the PureScript compiler’s issue tracker. John is also a long-time Haskell hacker.

Conclusion

PureScript is very easy for a Haskeller to learn. The most complicated things will be learning the PureScript/JavaScript ecosystem and deciding on a DOM manipulation library.

Good luck on your PureScript journey.

Footnotes


  1. It used to be, “Just use cabal and sandboxes. For multi-project builds… maybe try a small shell script?”

  2. This is not strictly true. Phil explains on reddit.

  3. Check out this article or this issue for more discussion.

  4. Pulp was originally written in JavaScript, which may be why it does not have the features one would expect from something like stack. However, it is currently being rewritten in PureScript, so maybe there is hope that it will be more flexible in the future.