Introduction

Recently, I’ve been playing with PureScript1, a functional, Haskell-like, language which compiles to JavaScript. These are my notes on the adventure, and lean heavily on comparisons with Haskell.

My motivation was the desire to build little webpage widgets, mainly because I’ve wanted to be able to embed little demonstrations into online articles. One could write these in JavaScript directly, but I given a free choice, I’d rather write Haskell. Sadly though the anecdotal evidence I have for doing this with something like ghcjs2 is that it is a painful experience.

Instead the cool kids seem to like elm3, and PureScript4. At a cursory level, the latter looked nicer to my eye.

Comparison with Haskell

It is clear that PureScript owes a lot to Haskell. For example, the “Hello World” example on PureScript’s homepage is readable to anyone familiar with Haskell:

import Prelude 				
import Control.Monad.Eff.Console (log)	
					
greet :: String -> String		
greet name = "Hello, " <> name <> "!"	
					
main = log (greet "World")		

Of course there are differences. Indeed the PureScript repo on GitHub has a page discussing them5.

Prelude

In the first line above we explicitly import the Prelude, whereas in Haskell this happens implicitly. In this sense Haskell is perhaps more opinionated, and as with all opinions some people disagree. The Haskell Wiki discusses how to avoid importing the Prelude6, and it has become quite fashionable to promote changing the Prelude7.

To some extent I think PureScript embraces many ideas popular in the contemporary Haskell community, rather than say Haskell 20108.

For example, if you look in old Haskell documentation, you’ll find that:

maximum :: Ord a => [a] -> a

but today, after the FTP9, it is polymorphic:

maximum :: (Foldable f, Ord a) => f a -> a

PureScript follows the modern convention:

> import Data.Foldable
> :t maximum
forall a f. Ord a => Foldable f => f a -> Maybe a

You’ll also see that the forall keyword, which is often suppressed in Haskell10 remains in PureScript.

Strict

A more substantive difference between the languages is that Haskell is lazy11, but PureScript is strict12.

One of the standard arguments in favour of laziness is that it allows you to compose things more easily because it separates definition from evaluation. Sure enough, I quickly found an example which I would implement using an infinite list in Haskell: rejection sampling13 to generate random numbers.

This is a technique for taking random samples from some distribution, where sometimes the sample is invalid and needs to be rejected: specifically I’ll write code which returns a Just Double if things go well and Nothing otherwise. If I get the latter I need to sample again until I get something. Inevitably there’s hidden state here—otherwise there would be no point in repeating the calculation—so the whole thing lives in a monad.

For example, suppose I want to generate uniform deviates on [0,0.5[ by taking deviates on [0,1.0[ then rejecting any greater than 0.5. This is not a sensible solution but demonstrates the technique. Here's some Haskell:

import System.Random 			
import Data.Maybe			
					
foo :: Double -> Maybe Double		
foo x | x < 0.5   = Just x		
      | otherwise = Nothing		
					
randomFoos :: RandomGen g => g -> [Double]
randomFoos = catMaybes . map foo . randoms

Which you can run thus:

ghci> take 2 . randomFoos <$> getStdGen
[0.390256615512681,0.33410830694521]

All reasonably straightforward: generate an infinite list of randoms, mark some as rejected, then keep the rest. We need the applicative <$> because the generator is in IO.

By contrast, in PureScript we need more machinery:

untilJust :: forall a eff. Eff eff (Maybe a) -> Eff eff a	
untilJust f = f >>= go						
    where go Nothing  = untilJust f				
          go (Just x) = pure x					
								
foo :: forall eff. Eff (random :: RANDOM | eff) (Maybe Number)	
foo = do							
        u <- random						
	pure $ if u < 0.5 then (Just u) else Nothing		
								
randomFoo :: forall eff. Eff (random :: RANDOM | eff) (Number)	
randomFoo = untilJust foo

This is a slightly unfair example in the sense that the Haskell infinite list consumes the entire future supply of random numbers. That’s fine if all the randomness you want is concentrated in those numbers, but otherwise you’ll need to split14 the generator first. So, more machinery, and a constraint on the random algorithms available.

Row polymorphism

In the example above, you can see the Eff monad15 where PureScript encodes the details of the environment. It’s a bit like IO in Haskell.

However, in PureScript, we have fine-grained control over the effects with row polymorphism16. In the example above, you can read the type of foo as saying we need an Eff monad with RANDOM functionality and whatever else you like.

For example this type:

main :: Eff (console :: CONSOLE, random :: RANDOM) Unit

means that we have both RANDOM and CONSOLE access. The lack of the | eff bit, makes the row of effects closed: we are saying that our code doesn’t use any other effects, even in a subcomputation.

In Haskell, I think the natural way to tackle this would be with Monad Transformers.

Stack overflow

The only proper crash17 I managed to create with PureScript was blowing the stack when looping over graphics calls in the CANVAS part of Eff. The short version is that this works:

foreachE (1..100000)
  fillRect ...

but this doesn’t:

for (1..100000)
  fillRect ...

Useful information

The canonical website for PureScript is purescript.org18. There is also a wealth of information on GitHub19, and a fine book20.

For specific API documentation, Pursuit21 is the place to go.

Cookbook

Most of the time you accomplish tasks using pulp22 or bower23. Both projects are well documented, and what follows is a subjectively useful subset.

Building for the browser

Given a fresh repository, this is how I build stuff to deploy in a browser:

$ npm install
$ bower install
$ pulp browserify --optimize --to dist/Main.js
$ open html/index.html

Obviously you don’t need to run the install commands subsequently.

Starting a new project

To start a new project, use pulp:

$ pulp init

Having done this, the key information is saved in bower.json e.g.

$ cat bower.json				
{						
  "name": "foo",				
  "ignore": [					
    "**/.*",					
    "node_modules",				
    "bower_components",				
    "output"					
  ],						
  "dependencies": {				
    "purescript-prelude": "^3.1.1",		
    "purescript-console": "^3.0.0"		
  },						
  "devDependencies": {				
    "purescript-psci-support": "^3.0.0"		
  }						
}

Adding a library

To add a new library, and update bower.json:

$ bower install purescript-foo --save

The REPL and other executions

To launch psci, the REPL:

$ pulp repl

To run the code in node:

$ pulp run

To run the tests:

$ pulp test

Browser Support

Much of the time, I write PureScript to deploy in a browser. To do this we need an API. Happily the popular React24 JavaScript library is both already packaged25 and described in the book.

By and large it was easy to use, and were I starting afresh I’d use it again. There is a higher-level library called Thermite26, but I preferred to stick to something used widely in JavaScript land.

Given that I wanted to embed a widget written in PureScript into a webpage which was written elsewhere, I could assume that the widget would be simply connected rather than spread over the page, or interleaved with explanatory text. This was fortunate, because I couldn’t quite see how to do that.

Another problem I had concerned layout: it seemed hard to make the widget’s width 80% of the container. This issue comes up when people use React in Javascript too: on Stack Overflow27, or facebook’s react repo28. Rather than translate those solutions to PureScript, it sufficed to set the width of the top-level React object with a simple CSS rule.

Canvas

For graphics, there are PureScript bindings29 to the HTML5 Canvas Element30 which works just as you’d expect.

Conclusions

The zeroth order conclusion is that this works. I found PureScript a good way to write Haskell like code which can then be deployed in the browser. The toolchain is easy to install; the abstractions are good so you don’t have to write PureScript but debug JavaScript.

Browser integration works well too: React is pleasant, and the Canvas works as you’d expect.

That all said, I still prefer Haskell. If nothing else PureScript’s convinced me that lazy really is best, and Haskell stills seems classier than anything else.

I suppose the final conclusion is that next time I want to write JavaScript I’ll write PureScript instead, but given the choice I’d still prefer to write Haskell.