0% found this document useful (0 votes)
8 views111 pages

Comparing Elm and PureScript Features

The document compares Elm and PureScript, highlighting differences in module imports, formatting, primitive types, and type systems. It discusses unique features like the handling of unit and void types, syntactical differences, and pattern matching approaches. Additionally, it covers type definitions, including newtypes and type aliases, as well as the usage of guards and local value declarations.

Uploaded by

bin hou
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views111 pages

Comparing Elm and PureScript Features

The document compares Elm and PureScript, highlighting differences in module imports, formatting, primitive types, and type systems. It discusses unique features like the handling of unit and void types, syntactical differences, and pattern matching approaches. Additionally, it covers type definitions, including newtypes and type aliases, as well as the usage of guards and local value declarations.

Uploaded by

bin hou
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Elm vs PureScript

1
Automatically Imported Modules

In Elm, many modules were automatically imported so you didn't


have to import them. But it was difficult to know what was
included.

The only way to tell was to wait until the compiler complained.

In PureScript, there is a single module called Prelude that you can


import to get the basics of PureScript. Prelude imports many
modules and re-exports part or all of those modules.

2
NO elm-format

You are responsible for formatting and WHITESPACE matters.

Suggestion: Set your Tabs to 2 SPACES


-- This is valid
add x y = x
+ y -- the indentention tells the compiler
-- this is a continuation of the previous line

-- This is NOT valid


add x y = x
+ y

3
NO elm-format (cont.)

f x =
let y = x
+ 10 -- the indent has to be deep enough
-- so it's indented past the `y`
-- NOT the `let`

4
Primitive Types

Elm has Int and Float.

PureScript has Int and Number.

Number corresponds to a Javascript Number.

They both have String and Char.

5
Unit Type

Elm has the Unit Type () which has ONE AND ONLY ONE
Inhabitant called ().

PureScript has Unit Type called Unit which has ONE AND ONLY
ONE Inhabitant, called unit.

PureScript uses () for a different purpose, viz. an Empty Row Type


(more about this later).

6
Unit Type (cont.)

In Elm, () is implemented by the compiler.

7
Unit Type (cont.)

In PureScript, Unit is defined as:

foreign import data Unit :: Type

And unit is imported as:

foreign import unit :: Unit

And implemented in JavaScript:

"use strict";

[Link] = {};
8
Void Type

Elm has the Void Type Never which has ZERO Inhabitants since
you cannot construct something of type Never.

PureScript has the Void Type Void which has ZERO Inhabitants
since you cannot construct something of type Void.

9
Void Type (cont.)

In Elm:

type Never = JustOneMore Never

Here, the only way to create a Never is to give the Data Constructor
JustOneMore something of type Never which can only be created by using
JustOneMore which needs a Never and on and on forever.
JustOneMore (JustOneMore (JustOneMore (JustOneMore ...)))

Another way they make it impossible to create a Never is NOT EXPOSE


the Data Constructor, JustOneMore.
10
Void Type (cont.)

In PureScript:
newtype Void = Void Void

They use the same approach as Elm uses. The definition is


INFINITELY Recursive making it impossible to write:
Void (Void (Void (Void ...)))

They also do not export the Data Constructor, also called Void.
(more about newtype later)

11
Void Type (cont.)

There are times where you want to handle something of type Void:
rightOnly :: forall t . Either Void t -> t -- more on forall later
rightOnly (Left v) = ????????????
rightOnly (Right t) = t

Here the Left case is impossible to write. We need to take the Void,
v, and return something of type t.

We need a function that takes a Void and returns any type.

But how this possible?


12
Absurd

In PureScript, there is a function called absurd that takes a Void


and returns any type.
absurd :: forall a. Void -> a

Basically, it says that IF YOU CAN GIVE ME A VOID, I'LL GIVE YOU
SOMETHING OF ANY TYPE.

This is why it's called absurd.

13
Absurd (cont.)

An implementation of absurd:

absurd :: forall a. Void -> a


absurd (Void x) = absurd x

Since this function can never be called, all it has to do it satisfy the Type Checker.

It basically unwraps the first of an Infinite number of Voids and then Infinitely
recurses.

This keeps the Type Checker quite happy since absurd takes a Void which it gets
from x.

14
Absurd (cont.)

Now we can fully write the rightOnly function:


rightOnly :: forall t . Either Void t -> t
rightOnly (Left v) = absurd v
rightOnly (Right t) = t

Here v is a Void and that's exactly what absurd wants. And since it
turns that into ANY TYPE, it unifies with t.

And we never have to worry that it'll be called because no one can
ever construct a Left Void.

15
Some more formatting examples
let s = 10 -- ALL variables are aligned under the `s`
t = "This is a really long string"
<> "another long string" -- the easiest way by just using tab (more about <> later)
u = "You can also indent like this"
<> "If you like this better" -- here you have to indent the first line
v = "Not my favorite way"
<> "to indent, but still valid" -- here this line is indented by ONLY 1
w = 20 in

The let can be all on one line or spread out.

let s = 10 in

let t = 20
in

let
u = 30
in

16
Syntactical Differences - Apostrophe/Prime

Elm can no longer have a prime in the name. They removed it from the
Language 2 versions back.

But sometimes you want a slight variation on a function without having to


think of a whole different name:

validate :: Int -> Either String Int


validate v = if v > 100 then Left "Too large" else Right v

validate' :: Int -> Maybe Int


validate' v = case validate v of
Left _ -> Nothing
Right x -> Just x
17
Syntactical Differences - Apostrophe/Prime (cont.)

Another cool thing (albeit of limited value) is you can actually


name you function after a contraction:
ain't :: Boolean -> Boolean
ain't = not

18
Syntactical Differences - Apply

Instead of <| use $


f x <| g y -- Elm

f x $ g y

Instead of |> use #


g y |> f x -- Elm

g y # f x

19
Syntactical Differences - Type Signatures

Instead of : in function definitions, use ::


multiply :: Number -> Number -> Number
multiply x y = x * y

Instead of :: for cons, use :


binary :: Array Int -- more on Array later
binary = 0 : [1]

20
Syntactical Differences - Compose

Instead of >> use >>>


filter filterEntry >>> head

Instead of << use <<<


not <<< null myList -- reads left to right

Usually, you want to pick the appropriate composition operator to


make the code read best.
null myList >>> not -- NOT AS CLEAR
21
Type differences
In Elm, you only have one way to create a new type by using the keyword type.

In PureScript, there are two ways, data to define a data type or newtype to define a new name to
an existing type (this is NOT an alias).

Elm:

type Maybe a = Nothing | Just a


type LastName = LastName String

PureScript:

data Maybe a = Nothing | Just a


newtype LastName = LastName String

22
Type differences - newtype
newtype must take ONLY 1 constructor and that constructor must take ONLY
1 argument.

newtype Pixels = Pixels Number


newtype Inches = Inches Number

This makes it impossible to pass Pixels to a function that wants Inches and
vice versa even though they both contain Number.

BTW, using these newtypes doesn't have any more overhead at runtime than
just using Number. The real benefit is that the compiler will treat them as
different types.
23
Type differences - Aliases
Type aliases are just a different name for an existing type. Many
times, these are used to make reading types easier. In Elm:
type alias MiddleName = String -- Elm

becomes:
type MiddleName = String -- PureScript

N.B. There is NO protection against passing any arbitrary String to


a function that expects a MiddleName.
24
Type differences - Records
With records, Elm uses type alias. In PureScript, you only use
type:
type SomeRecord =
{ val :: Int
, rec ::
{ val2 :: Int
, name :: String
}
}

N.B. this formatting is idiomatic PureScript.


25
String Literals
s1 :: String
s1 = "This is a string on one line"

s2 :: String
s2 = "This is a string\
\ on two lines" -- everything before the \ is ignored

s3 :: String
s3 = """
This is a
string on
three lines
"""

s3' :: String
s3' = "\nThis is a\nstring on\nthree lines\n" -- same as `s3`

26
String Literals with Regex
Triple quotes makes typing Regex strings much easier since you
don't have to escape backslashes.
regex ".+@.+\\..+" noFlags

versus:
regex """.+@.+\..+""" noFlags

27
Boolean differences
• Elm

Bool is just another Sum Type.


type Bool = True | False

• PureScript

Boolean is a Primitive Type that has inhabitants true and false.

N.B. They are LOWERCASE literals echoing Javascript booleans.

28
Lists and Arrays
PureScript Lists are linked lists just like Elm. They can be defined
by importing [Link].

PureScript Arrays are like Javascript arrays and can be used by


importing [Link].

You will use Array far more than List.

29
Lists and Arrays - Literals
Arrays literals can be defined as:
[1, 2, 3]
["abc", "xyz"]

Lists must be done using the Cons constructor or the : operator:


Cons 1 (Cons 2 (Cons 3 Nil)) -- (1 : 2 : 3 : Nil)
1 : 2 : 3 : Nil -- (1 : 2 : 3 : Nil)
Cons "abc" (Cons "xyz" Nil) -- ("abc" : "xyz" : Nil)
"abc" : "xyz" : Nil -- ("abc" : "xyz" : Nil)

30
Lists and Arrays - Literals (cont.)
You can also use fromFoldable from [Link] to convert an
Array literal to a List:
import [Link] (fromFoldable)

[1, 2, 3] # fromFoldable -- (1 : 2 : 3 : Nil)


["abc", "xyz"] # fromFoldable -- ("abc" : "xyz" : Nil)

31
Let and Where
In Elm, you can only introduce local values in a let block. The let
block must come at the BEGINNING of a declaration.

In PureScript, you can also use let, but there is an additional


mechanism using where which introduces values at the END.

32
Let and Where (cont.)
let vs where:
f :: Int -> Int
f x = let multiplier = 10 in
multiplier * x

f' :: Int -> Int


f' x = multiplier * x
where multiplier = 10

N.B. you can create alternative versions of a function or value and name
it identically except for adding a prime (single quote) to distinguish it.

33
Let and Where (cont.)
Whether to use let or where is more about personal taste.

If you use let, then it requires the reader to first understand the
nuance of the function, before understanding the concept.

Using where, starts with the concept of what the function does and
then burdens the reader with the details as an after thought.

34
Guards
Guards allow you to place a condition on a function's computed value by using a vertical bar, |.

You can write a single instance of the function:

isEmpty :: forall a. Array a -> Boolean


isEmpty xs
| length xs == 0 = true
| otherwise = false -- otherwise is defined to be true

Or you can write multiple instances:

isEmpty :: forall a. Array a -> Boolean


isEmpty xs | length xs == 0 = true
isEmpty _ = false

35
Guards (cont.)
Most of the time, guards are easier to read than if logic. Compare the difference
between a function with if-then-else and it's equivalent using guards:

data LargeMediumSmall = Large | Medium | Small

largeMediumSmall' :: forall a. Array a -> LargeMediumSmall


largeMediumSmall' xs =
if length xs <= 10 then Small
else if length xs <= 20 then Medium
else Large

largeMediumSmall :: forall a. Array a -> LargeMediumSmall


largeMediumSmall xs
| length xs <= 10 = Small
| length xs <= 20 = Medium
| otherwise = Large

36
Guards in Case Statement
Guards can also be used in case statements:
noBiggerThan10 :: Maybe Int -> Int
noBiggerThan10 x = case x of
Just x | x > 10 -> 10
| otherwise -> x
Nothing -> 0

The pattern match is to the left of the |. Then the condition, an


arrow (because of the case) and finally the value to the far right.

37
Pattern Matching differences
Elm MUST use a case statement to pattern match.
{-| `bool` performs case analysis for the `Boolean` data type, like an `if` statement.
-}
bool : Bool -> a -> a -> a
bool b x y =
case b of
True ->
x

False ->
y

38
Pattern Matching differences (cont.)
PureScript can specify different versions of the function with
different patterns.
-- | `bool` performs case analysis for the `Boolean` data type, like an `if` statement.
bool :: forall a. Boolean -> a -> a -> a
bool true x _ = x
bool false _ y = y

39
Pattern Matching differences (cont.)
Elm must be total whereas PureScript can be partial, with a bit of extra cruft.

import [Link] (unsafePartial)

unsafeBool :: forall a. Boolean -> a -> a -> a


unsafeBool = unsafePartial \true x _ -> x

If bool is called with false 1 2, then the following Runtime Error will be
produced:

bool false 1 2
Failed pattern match

40
Pattern Matching differences (cont.)
In Elm, patterns can be named with an as and a name. Here tup is bound to the tuple (x, y):

maxTuple : (Int, Int) -> Result String Int


maxTuple (x, y) as tup =
if x > y then Ok x
else if x < y then Ok y
else Err <| "No max for tuple: " ++ tup

In PureScript, patterns can be named with a name and an @ symbol. Here the pattern is matched
with [x, y] and the full array is bound to the name arr.

sortPair :: Array Int -> Array Int


sortPair arr@[x, y]
| x <= y = arr
| otherwise = [y, x]
sortPair arr = arr -- don't sort any array that's not a pair

41
Pattern Matching - Lists
Any arbitrary length List can be pattern matched. Unlike Elm, Lists in
PureScript are defined as a Sum Type (Union Type in Elm).
-- this definition can be found in [Link]
data List a = Nil | Cons a (List a)

Which means that they must be pattern matched using Nil and the
Cons constructor.
length :: forall a. List a -> Int
length Nil = 0
length (Cons x xs) = 1 + length xs
42
Pattern Matching - Lists (cont.)
You can also pattern match with the Cons operator:
length :: forall a. List a -> Int
length Nil = 0
length (x : xs) = 1 + length xs
-- length (Cons x xs) = 1 + length xs

43
Pattern Matching - Arrays
Arrays can ONLY be pattern matched with a known length. So you cannot write a
length function in PureScript.

Instead, PureScript implements length in Javascript by using a Foreign Function


interface.

foreign import length :: forall a. Array a -> Int

Calling the Javascript function:

[Link] = function (xs) {


return [Link];
};
44
Pattern Matching - Lists and Arrays
isEmpty for Lists:
isEmpty :: forall a. List a -> Boolean
isEmpty Nil = true
isEmpty _ = false

isEmpty for Arrays:


isEmpty :: forall a. Array a -> Boolean
isEmpty [] = true
isEmpty _ = false

45
Pattern Matching - Records
type Address = { street :: String, city :: String }

type Person = { name :: String, address :: Address }

-- simple
isAlice :: Person -> Boolean
isAlice { name: "Alice" } = true
isAlice _ = false

-- nested
livesInLA :: Person -> Boolean
livesInLA { address: { city: "Los Angeles" } } = true
livesInLA _ = false
46
Pattern Matching - Records (cont.)
Record Puns can be used to destruct or construct records.
data Point = Point {x :: Int, y :: Int}

-- Destruct
showPoint :: Point -> String
showPoint (Point { x, y }) = "(" <> x <> ", " <> y <> ")"

origin :: Point
origin = Point { x, y } -- Construct
where
x = 0.0
y = 0.0
47
Documentation comments
Comments that start with a pipe character, |, are considered
documentation, and will appear in the output of tools like psc-docs
and Pursuit.
-- | `multiply` does a multiplication
-- This line will NOT be included in docs
-- | This line will thanks to the PIPE character
multiply :: Number -> Number -> Number
multiply x y = x * y

48
Records
Records in Elm and PureScript are similar, e.g. both support Row
Polymorphism.

A record has fields also known as a Row.


type Person =
{ name :: String
, age :: Int
}

Here name and age make up a Row.

49
Records - Alias vs Constructor
Records can be defined as simple aliases:
type RA = {a :: String}

ra :: RA
ra = {a: "aaa"}

Or with constructors:

newtype R = R {a :: String}

r :: R
r = R {a: "aaa"}
50
Records - Alias vs Constructor (cont.)
newtype R = R {a :: String}

When we use a Constructor, we AUTOMATICALLY get a function R:


> newtype R = R {a :: String}
> :t R
{ a :: String
}
-> R

We do NOT get this when we use a type alias with type.

51
Records - Row Polymorphism
Now consider another record.

type Restaurant =
{ name :: String
, address :: String
}

And imagine we want to write a function to get the name of Person and Restaurant.

getPersonName :: Person -> String


getPersonName person = [Link]

getRestaurantName :: Restaurant -> String


getRestaurantName restaurant = [Link]

52
Records - Row Polymorphism (cont.)
But these 2 functions are doing practically the same thing.
Wouldn't it be nice to have a single function that could do both?

In other words, we'd like to write a Polymorphic Function, i.e. one


that can take a record of any type.

We cannot, however, write the following:


-- will NOT compile !!!!!
getName :: forall a. a -> String
getName record = [Link]

53
Records - Row Polymorphism (cont.)
We need to be a bit more specific than a since a is any type NOT
just records.

We want functions that take a record argument with very specific


names and types in the Row. And we want those functions to be
Polymorphic for all the other names and types that are ALSO in
that same Row.

Hence the term, Row Polymorphism.

54
Records - Row Polymorphism (cont.)
In Elm we write
getName : {a | name : String} -> String
getName record = [Link]

Or we can write
getName : {a | name : String} -> String
getName {name} = name

55
Records - Row Polymorphism (cont.)
In PureScript we write

getName :: forall r. {name :: String | r} -> String


getName record = [Link]

Or we can write

getName :: forall r. {name :: String | r} -> String


getName {name} = name

Or with renaming

getName :: forall r. {name :: String | r} -> String


getName {name: n} = n

56
Records - Accessors
To access a field in a row of a record in Elm
.name record

In PureScript
_.name record -- evaluates to `[Link]`

N.B. in PureScript we are using what is called a Wildcard, i.e. the


underscore. (more on Wildcards later)

57
Records - Updates
To update a record in Elm
{record | name = "Elm"}

In PureScript
record {name = "PureScript"}

N.B. Updates use the EQUAL SIGN and NOT the colon. This is the
ONLY time the EQUAL SIGN is used in Records.

58
Records - Nested Updates
type SomeRecord =
{ val :: Int
, rec ::
{ val2 :: Int
, name :: String
}
}

We would initialize a variable of type SomeRecord using the following syntax:

r :: SomeRecord
r =
{ val: -10
, rec:
{ val2: 20
, name: "Bob"
}
}

59
Records - Nested Updates (cont.)
r :: SomeRecord
r =
{ val: -10
, rec:
{ val2: 20
, name: "Bob"
}
}

In Elm, updating name from Bob to Fred


[Link] -- we have to capture the nested record
|> \rec -> {rec | name = "Fred"} -- then modify this nested record
|> \newRec -> {r | rec = newRec} -- then update the original record

60
Records - Nested Updates (cont.)
r :: SomeRecord
r =
{ val: -10
, rec:
{ val2: 20
, name: "Bob"
}
}

In PureScript
r {rec {name = "Fred"}} -- all in one step

61
Records - Wildcards
{name: _, age: _}

is equivalent to
\name age -> {name: name, age: age}
-- or more concise
\name age -> {name, age}

Note that the order of the underscore matters.

Also, note that since we are CREATING a record, it uses the colon
syntax.
62
Records - Wildcards (cont.)
{name: _, age: _}

We can use this function to initialize a record:


{name: _, age: _} "Joe Mama" 52

63
Records - Wildcards (cont.)
_ { name = "Victor" }

is equivalent to
\rec -> rec { name = "Victor" }

Note that this is UPDATING a record since it's using the equals
syntax.

64
Records - Wildcards (cont.)
_ { name = "Victor" }

We can use this function to change the name in any record with a
name field:
record # _ { name = "Victor" }

Or we can use the simpler syntax:


record { name = "Victor" }

65
Records - Setters and Getters
In Elm, we have Getters for Records but no Setters:
.name -- Getter

In PureScript, we have BOTH:


_.name -- Getter
_ { name = "Victor" } -- Setter with a constant value
_ { name = _ } -- Setter (takes 2 params, record and value)

66
Records - Extensible
Elm:

type alias Nameable a =


{ a
| firstName : String
, lastName : String
}

PureScript:

type Nameable r =
{ firstName :: String
, lastName :: String
| r
}

67
Records - Extensible (cont.)
Extensible records can be extended in the following ways:

type Nameable r =
{ firstName :: String
, lastName :: String
| r
}
type Person = Nameable (age :: Int)
type FictionalPerson = Nameable (age :: Int, wingspan :: Int)

Notice how parenthesis are used instead of the record syntax like in Elm.

This makes more sense since r isn't replaced with a record but with a Row Type.

68
Records - Syntactical Sugar
A Record:

type Person =
{ name :: String
, age :: Int
}

is just Syntactical Sugar for:

type Person =
Record
( name :: String -- N.B. Row Type
, age :: Int
)
69
Binary Operators
In Elm, the freedom to create your own Binary Operators was removed in version
0.19, but in PureScript this powerful feature is supported.

But, you can ONLY create an operator after you've created a named function for
the operation. For example:

data List a = Nil | Cons a (List a)

append :: forall a. List a -> List a -> List a


append xs Nil = xs
append Nil ys = ys
append (Cons x xs) ys = Cons x (append xs ys)

infixr 5 append as <> -- the 5 is Precedence (explained later)


70
Binary Operators (cont.)
Our append operator can now be used.

import [Link] (List, fromFoldable, toUnfoldable)

l1 :: List Int
l1 = [1, 2] # fromFoldable -- (1 : 2 : Nil)

l2 :: List Int
l2 = [3, 4] # fromFoldable -- (3 : 4 : Nil)

l3 :: List Int
l3 = l1 <> l2 -- (1 : 2 : 3 : 4 : Nil)

l3' :: Array Int


l3' = l1 <> l2 # toUnfoldable :: Array Int -- [1, 2, 3, 4]
71
Binary Operators - Associativity
There are 3 types of operator associativities.
• infixr - Right
• infixl - Left
• infix - None

72
Binary Operators - Associativity (cont.)
Right associative, i.e. infixr, implies right parenthesis
l1 <> l2 <> l3 <> l4
-- is equivalent to
(l1 <> (l2 <> (l3 <> l4)))

Left associative, i.e. infixl, implies left parenthesis


n1 + n2 + n3 + n4
-- is equivalent to
(((n1 + n2) + n3) + n4)

73
Binary Operators - Associativity (cont.)
Non-associative, i.e. infix, implies NO parenthesis. This means
that repeated use of this operator (or with other non-associative
operators) may NOT be used without EXPLICIT parenthesis. For
example:
true == true == true -- will NOT compile
(true == true) == true -- will compile

74
Binary Operators - Precedence
In Prelude, * is precedence 7 (higher) and + is precedence 6
(lower).

Therefore, the following:


2 * 3 + 4

is equivalent to:
(2 * 3) + 4

75
Binary Operators - Precedence (cont.)
infixr 5 append as <>

Here our operator <> is Right-associative at a precedence 5


(meaning it gets evaluated BEFORE anything that's Precedence 4,
3, 2, 1 and 0).

76
Binary Operators - Fixity
Reminder: infix stands for infixed operator, i.e. one that is in
between two values, e.g.:
l1 <> l2

This is opposed to a prefixed operator, i.e. one that is BEFORE two


values, e.g.:
append l1 l2

77
Binary Operators - Fixity (cont.)
It is possible to use operators in prefix mode:
(<>) l1 l2

And functions in infix mode by adding BACKTICKS:


l1 `append` l2

78
Binary Operators - Sections
Imagine you have a function that halves a number.

In Elm:
\x -> x / 2

But in PureScript, you can use a Operator Section:


(_ / 2)

using underscore as the placeholder for missing number.

79
Binary Operators - Sections (cont.)
And to do the inverse.

In Elm:
\x -> 2 / x

PureScript:
(2 / _)

80
Binary Operators - Sections (cont.)
The PureScript approach is much more readable than a lambda
expression.

It also reduces mistakes and cognitive overhead when using


operators in prefix mode.
flip (-) 2 -- Elm

versus
(_ - 2) -- PureScript

81
Tuples
Unfortunately, there is no special syntax for Tuples as in Elm and
Haskell.

They are just a normal Product Type:


data Tuple a b = Tuple a b

82
Tuples (cont.)
We use them just like any other type. In Elm:
fst : (a, b) -> a
fst (x, _) = x

Becomes the more verbose:


import [Link] (Tuple(..))

fst :: forall a b. Tuple a b -> a


fst (Tuple x _) = x

83
Case statements
Case statements have a few more options in PureScript, e.g.
guards, wildcards and multiple case values.

In Elm, we use tuples to do a case on multiple values:


f : Maybe Bool -> Maybe Bool -> String
f x y =
case (x, y) of
(Just True, Just True) -> "Both are True"
(Just False, Just False) -> "Both are False"
_ -> "They are different"

84
Case statements (cont.)
In PureScript, we don't have the nice tuple syntax, so we can do
multiple values by just separating the values with a comma:
f :: Maybe Bool -> Maybe Bool -> String
f x y =
case x, y of
Just true, Just true -> "Both are true"
Just false, Just false -> "Both are false"
_ -> "They are different"

N.B. True and False or lowercase in PureScript.

85
Case statements - Guards
You can use guards, instead of if, then and else, in your case
statements. This makes the logic easier to read:
f :: Either Int Unit -> String
f x = case x of
Left x | x == 0 -> "Left zero"
| x < 0 -> "Left negative"
| otherwise -> "Left positive"
Right _ -> "Right"

86
Case statements - Wildcards
In Elm, if you do a case in a lambda expression, then you MUST make a
variable to use in the case.

This isn't just verbose, but burdens us with naming yet another variable. One
of the benefits of a lambda is NOT having to name the temporary function.

Well, in PureScript you don't have to name the case variable if it's involved in
a lambda.

This is accomplished with the LambdaCase extension in Haskell, but is


built-in to PureScript by using the wildcard syntax.

87
Case statements - Wildcards (cont.)
In Elm:

getCount
|> \count -> case count of
0 -> "Zero"
1 -> "One"
_ -> "More"

In PureScript:

getCount # case _ of
0 -> "Zero"
1 -> "One"
_ -> "More"
88
Type Holes
Many times we cannot figure out what type something value or
function is. In Elm, you can't ask the compiler help directly.

You can explicitly make something an Int and then investigate the
compiler error message to hopefully discern the appropriate type.

In PureScript, there are things called Type Holes which are named
variables that start with a question mark, For example:
?whatGoesHere or ?help.

89
Type Holes (cont.)
Imagine you have the following code:
a :: Array Int
a = ?whatGoesHere (_ + 1) [1,2,3]

but you cannot remember what function you want to call. The
compiler will now give you an error with the type that was inferred:

90
Type Holes (cont.)
Hole 'whatGoesHere' has the inferred type

(Int -> Int) -> Array Int -> Array Int

You could substitute the hole with one of these values:

[Link].liftA1 :: forall f a b. Applicative f => (... -> ...) -> ... -> ...
[Link].liftM1 :: forall m a b. Monad m => (... -> ...) -> ... -> ...
[Link] :: forall a b. Ord b => (... -> ...) -> ... -> ...
[Link] :: forall a b f. Functor f => (... -> ...) -> ... -> ...
[Link] :: forall i f a b. FunctorWithIndex i f => (... -> ...) -> ... -> ...
[Link] :: forall m. Monoid m => m
[Link] :: forall a b. a -> b

in value declaration a

91
Type Holes (cont.)
So now we can replace ?whatGoesHere with the map function:
a :: Array Int
a = map (_ + 1) [1,2,3]

92
Type Holes (cont.)
You can also use Type Holes to help just with types:
b :: ?help
b = map (_ + 1) [1,2,3]

Compiler error:
Hole 'help' has the inferred type

Array Int

in value declaration b
93
Type Holes (cont.)
You can also use an underscore (for types ONLY, doesn't work for functions). But
it's less useful and only produces a compiler WARNING.

c :: _
c = map (_ + 1) [1,2,3]

Compiler warning:

Wildcard type definition has the inferred type

Array Int

in value declaration c
94
Type Holes (cont.)
The biggest problem with type holes in PureScript is that your
program MUST compile before it'll give you an error on type holes.

This can be quite painful, especially when you're used to Haskell


type holes.

The best you can do is comment out as much code as you can to
get the code to compiler, sans the type hole which always
produces an error.

95
Universal Quantifier
Many of the examples in this presentation have used the forall in
Polymorphic Function definitions.

PureScript requires this to be added explicitly whereas in Elm it is


just implied. This is a burden for the reader and the writer of the
function.

In more advanced situations, the forall won't only reside at the


beginning of the function type definition.

96
Universal Quantifier (cont.)
For more on why the language designers decided to go this route,
see the Github Discussion where Fresheyeball, the author of many
Elm packages asks whether forall could be implicit like in Elm
and Haskell.

([Link]

97
Universal Quantifier (cont.)
So in Elm, we type:
identity : a -> a
identity x = x

and in PureScript all parameter type variables (lowercase) must be


included in the forall terminated by a period:
identity :: forall a. a -> a
identity x = x

98
Universal Quantifier (cont.)
Universal Quantifiers come from Predicate Logic and use the
familiar symbol, ∀. This makes reading universal qualification
much easier than forall.

We can type the symbol, ∀, instead of forall in our editors with a


little bit of work depending on your editor.

99
Universal Quantifier (cont.)
In Atom, you edit your [Link] file by choosing Snippets...
from the Atom menu and adding the following:
'.[Link]':
'Forall':
'prefix': 'fa'
'body': '∀'

Then when you're editing a PureScript file and type fa followed


immediately by the tab key, it'll replace it with ∀.

100
Universal Quantifier (cont.)
You can type the ∀ character on the Mac by first going into the
Character Viewer via Ctrl-Cmd-Space. Then go to the Math Symbols
section in the Left Pane. Find the ∀ character and double-click on it
to insert it into the current program.

You will need this to enter the ∀ in Atom's (or your editor's)
configuration.

101
Universal Quantifier (cont.)

102
Universal Quantifier (cont.)
The following is easier to read and less to type:
fst :: ∀ a b. Tuple a b -> a
fst (Tuple x _) = x

103
Functors
In Elm, they don't have a formal concept of Functors. But they have
lots of Functors, e.g. List, Maybe, Result, Decoder, Encoder, etc.

But because of this, you have to use the appropriate map function
from the corresponding package, e.g. [Link] on lists.

104
Functors (cont.)
In PureScript, you don't have to worry about the types since the
compiler will figure the types out for you.

Imagine the case of:


import [Link] (List, fromFoldable)

lms :: List (Maybe Int)


lms = [Just 1, Nothing, Just 14] # fromFoldable

105
Functors (cont.)
To map (_ + 1) over this we must map over the List and then over the Maybe. In Elm:

[Link] ([Link] \x -> x + 1) lms

PureScript:

map (_ + 1) <$> lms

Here <$> is the Operator for map, which, in this case, is mapping over the List with the
function map (_ + 1).

The explicit map is mapping over the Maybe.

106
Functors (cont.)
The <$> operator is a map function. The compiler figures out which
map to call based on the types.

You can think of <$> as applying a function to a Functor the same


way as $ applies function to values.

The operator <#> (flip map) is analogous to # in the same way, i.e.
the function comes AFTER.
map (_ + 1) <$> lms == lms <#> map (_ + 1)

107
Functors (cont.)
lms <#> map (_ + 1)

In this example, I would choose this syntax since it makes the


most sense to me.

I start with lms, a List, that I need to map over with <#>, which
contains Maybe Ints, which I need to map over to add 1.

108
Product and Sum Types

There are 2 classes of Types:


• Product - Tuples, Records
• Sum - Tagged Unions (or just Unions as Elm calls them)

Product types require multiplication to determine the number of


inhabitants.

Sum types require addition to determine the number of


inhabitants.
109
Product and Sum Types (cont.)
Starting with a Product Type P:

data Variant = This | That | TheOther


data P = P (Tuple Boolean Variant)

Here are all the inhabitants of Product Type P:

Tuple True This


Tuple True That
Tuple True TheOther
Tuple False This
Tuple False That
Tuple False TheOther

Note there are 2 inhabitants of Boolean and 3 inhabitants of Variant.


There are 2 * 3 = 6 inhabitants of P. Hence, Product Type.

110
Product and Sum Types (cont.)
Now for a Sum Type S:

data Variant = This | That | TheOther


data S
= ThingBool Boolean
| ThingVariant Variant

Here are all the inhabitants of Sum Type S:

ThingBool True
ThingBool False
ThingVariant This
ThingVariant That
ThingVariant TheOther

Note there are 2 inhabitants of ThingBool Boolean and 3 inhabitants of ThingVariant Variant.
There are 2 + 3 = 5 inhabitants of S. Hence, Sum Type.

111

You might also like