Types in Purescript

why Purescript is Awesome

First and foremost Purescript is a very strongly typed language.

All functions must have their types defined. This is a typical example of a function definition :

length :: Array -> Int

The length function above has to take an Array, and it is guaranteed to return an Int. There is no situation where anything else will happen - guaranteed!

Purescript has a compile stage where it will take all the Purescript code and convert it into Javascript. During this stage it will make sure all the types line up. If they don’t, it will refuse to compile and give you an error.

If we try to call length on anything that isn’t an Array :

length "This is not an array"

we get an error :

Error found:
in module Main
at src/Main.purs:11:29 - 11:51 (line 11, column 29 - line 11, column 51)

  Could not match type


  with type

    Array t0

while checking that type String
  is at least as general as type Array t0
while checking that expression "This is not a string"
  has type Array t0
in value declaration main

Algebraic Data Types

Types in Purescript can get pretty comprehensive.

We can have a type that contains more than one value (called a Product type) :

data Person = Person String String

-- Create a person
let john = Person "John" "Nhoj"

Or if we want to label each value, we can use a record type :

data Person = Person { firstName :: String
                     , lastName :: String
-- Create a person
let person = Person { firstName: "John"
                    , lastName: "Nhoj"

We can also create Sum types, a type that can be one of several different options :

data Colour = Red | Orange | Green

let colour = Orange
let anotherColour = Red

And we can mix them up :

data Planet = Mars | Neptune

data Customer = Human String String
              | Alien Planet String String String 
let onkonk = Alien Mars "Onk" "Onk" "Gonk" 
let bob = Human "Bob" "Smith"

With these simple types we can very richly, and very clearly model the types of data that our app will use. Where these types really shine is in pattern matching. If we want to do something with our type, we need to be able to pull the data apart to access the values inside. One option is with the case statement :

greeting :: Customer -> String
greeting customer =
  case customer of
    Human firstName lastName -> "Hello " <> firstName <> " " <> lastName
    Alien Mars mainName sporkName nameOfZork ->
        "Greetings " <> mainName <> " " <> sporkName <> " " <> nameOfZork 
    Alien Neptune mainName sporkName nameOfZork ->
        "Hail oh great " <> mainName <> " " <> sporkName <> " " <> nameOfZork

You can see that it is possible to cater differently for each possibility. In fact, if you don’t cater for every possibility you get an error. Say we decide we need to include a new planet :

data Planet = Mars | Neptune | Uranus

If we compile now we will get :

  A case expression could not be determined to cover all inputs.
  The following additional cases are required to cover all inputs:

    (Alien Uranus _ _ _)

This makes refactoring without fear a LOT easier. With properly setup data types when our data changes we can change the types and the compiler will tell us every place in the code that needs to be changed accordingly to handle the new scenarios. It is impossible to get null errors in Purescript - every possibility has to be catered for or your code won’t compile.