PureScript

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:

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:

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:

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:

The REPL and other executions

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

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.