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
String
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.