PureScript — An Intro for JavaScript Hackers

Constantin Dumitrescu
5 min readJul 26, 2016

--

PureScript is JavaScript with better syntax and awesome powers. Let’s try learning it without even looking at the docs — we’ll just look at what gets compiled.

Warning, heavy duty JavaScript ahead! You’ve been warned! :)

Let the fun begin!

Main.purs

Wow! That’s PureScript!
- it’s a module named Main
- it exports “result”
- has a few imports
- has an “add” function with a funny notation
- has a “result” with an even funnier notation

Let’s compile!*

Main.js

Alright!

  • it created a PS object (must be from PureScript).
  • the module became a function that populates PS.Main through the exports arg it gets.
  • the import Data.Maybe became a variable
  • import Prelude had 3 symbols:
    (+) defined what the “+” symbol does (wow!) and added a “ | 0 “ (check the link) to x + y
    (<$>) became Control_Apply.apply
    (<*>) became Data_Functor.map
  • add x y became a nested function. You’re allowed to specify function arguments on the left side of “=”, interesting!

result = add <$… became a very weird thing and we’ll need to take it apart:

var apply = Control_Apply.apply(Data_Maybe.applyMaybe)
var maybe = Data_Functor.map(Data_Maybe.functorMaybe)
var a = new Data_Maybe.Just(1)
var b = new Data_Maybe.Just(2)
var result = apply(maybe(add)(a))(b)

Here’s the initial and compiled version side by side for comparison:

result = add <$> Just 1 <*> Just 2
result = apply(maybe(add)(a))(b)

<$> became an apply statement that somehow wrapped the add statement in a maybe function. Maybe apply will invoke add with a and b or maybe it won’t.
<*> joined the two wrapped values in the same apply call.

OK. Let’s see what ELSE PureScript compiled (go backward and forwards between the module and these imports to see how they relate):

Onto the imports!

  • Data.Functor has two fns:
    →Functor is a constructor that takes a function sets it as a map method on the instance.
    map gets a dict (maybe it’s a Functor instance!) and returns the map function.
  • Control.Apply has two fns:
    Apply is a constructor that saves an object functor and an apply method to its instance.
    apply gets a dict (maybe it’s an Apply instance! :) ) and returns the apply function.
    (Pretty much the same thing as Data.Functor is you ask me…)
  • Data.Maybe (scroll to the bottom of the code to see it) has four fns:
    Just is a constructor wrapped in a self invoking closure, just stores a value
    Nothing is a constructor wrapped in a self invoking closure (again) but doesn’t do anything, it has a constructor just so it can be recognized at some point
    functorMaybe is an instance of Functor (see above) where:
    v1 is a value (because it is tested as an instance of Just)
    v
    is a function (because it gets called with v1 if v1 is a Just)
    and returns a Just with the value v(v1) or a Nothing.
    applyMaybe is an instance of Apply (see above) which is called with two functions that will end up on the Apply instance:
    first fn as this.functor
    second fn as this.apply.

Let’s analyze!

Going back to taking apart result. Let’s replace those earlier parts with the compiled code and get to the root of the program.

var maybe = Data_Functor.map(Data_Maybe.functorMaybe)// becomesvar maybe = function (v) {
return function (v1) {
if (v1 instanceof Just) {
return new Just(v(v1.value0));
}
return Nothing.value;
}
}
var apply = Control_Apply.apply(Data_Maybe.applyMaybe)// becomesvar apply = function (v) {
return function (v1) {
if (v instanceof Just) {
return maybe(v.value0, v1);
}
if (v instanceof Nothing) {
return Nothing.value;
}
}
}
var a = new Data_Maybe.Just(1)
var b = new Data_Maybe.Just(2)
// becomevar a = {
value0: 1
} // A Just instance
var b = {
value0: 2
} // A Just instance

Ok, let’s put everything back together and see what happens:

var result = apply(maybe(add)(a))(b)1. maybe(add) -> returns a function waiting for a value
2. maybe(add)(a) -> returns a Just object containing
a function. Why a function? Because calling add
with a returns a function wating for b.
3. apply(maybe(add)(a)) - returns a function waiting
for a value
4. apply(maybe(add)(a))(b) - calls the function that
was waiting for b and wraps the result in a Just
6. add(a, b) is just a + b

So the final answer, you guessed it, is:

result = add <$> Just 1 <*> Just 2
result -- Just 3

Phew, that was hardcore. This must be pretty powerful, what are other use cases for it? Check this out:

result = add <$> Just 1 <*> Nothing
result -- Nothing
result = add <$> (add <$> Just 1 <*> Just 2) <*> Just 3
result -- Just 6
result = add <$> (add <$> Just 1 <*> Nothing) <*> Just 3
result -- Nothing

Interesting! So no matter where an error or an unuseful value (think undefined or null) occurs in the program you don’t need to stop execution and throw or return an invalid value. You allow the error to flow through the program but nothing gets triggered — it keeps being Nothing until the bitter end. Your program doesn’t break anymore if you have an error, it just doesn’t do anything (unless you decide to inform the user that you got Nothing to show him :) )

Some lessons learned about PureScript

  • PureScript has an elegant syntax for modules. You can name them, specify their exports at the very top and import other modules in a beautiful fashion (prettier than import * as Foo from “Bar”)
  • You can define symbols, the + symbol just didn’t mean anything until you import it, although not covered here, you can add super powers to symbols
  • You have function arguments on the left side of the equal sign and you also don’t need to write “var” or “let”. Which one do you prefer?
// PureScript:
add x y = x + y
// JavaScript
var add = function (x) {
return function (y) {
return x + y
}
}
// or
let add = x => y => x + y
  • Very readable and logical compilation to JavaScript — both easy to debug and an excellent way to learn. When in doubt on what a keyword does, just compile and look at the code — you can learn a lot of interesting things that way, even advanced Functional Programming stuff that would be almost impossible to learn the “traditional” way without an extensive academic background.

If you made it this far, I congratulate you!
If you found this helpful or see potential in PureScript, don’t forget to share. Or leave a comment bellow. Thanks!

(*) Compile command in the root of the project: pulp build --to Main.js
Note 1: Check http://www.purescript.org/download/ for setting up PureScript on your environment!
Note 2: The compiled code is stripped down and reformatted a bit to aid in reading and understanding the code.

--

--