Maybe you've played around with functional programming before, but don't consider yourself a functional programmer. Or maybe you program functionally, but only in a dynamically-typed programming language. Or MAYBE you just like workshops with really long, ridiculously-sounding titles!
No matter, this workshop that teaches the super hot programming language PureScript is guaranteed to cure what ails you!
Come, learn about functions, learn about types, learn about data, and learn about how to smash them all together to build beautiful programs that are easy to test, easy to combine, and easy to reason about.
Become the functional programmer you were born to be!
8. The Real Deal
1. Totality. Every element in domain must be mapped to some
element in codomain.
2. Determinism. Applying a func:on with the same value (in
domain) results in same value (in codomain).
9. Exercise Set 1
data CharacterStatus = Content | Hungry | Tired
describeCharacterStatus :: CharacterStatus -> String
1. Create a set called CharacterStatus, which represents the
different states of the player.
2. Create a func9on called describeCharacterStatus which
describes the state of the player.
11. Literal Types
• String : The set that contains all strings; "foo" is an element of this set.
• Number : The set that contains all numbers;0
5.5 is an element of this set.
• Int : The set that contains all integers; 3 is an element of this set.
• Char : The set that contains all characters; 'J' is an element of this set.
• Boolean : The set that contains the values true and false.
Plus records {foo: "bar"} and arrays [1, 2, 3]!
0
Not really, $%#@&!!
12. Product Types1
data Loc = Loc Int Int
1
They get their name from an easy way you can use to compute the size of these sets (hint: product = mul;plica;on).
18. Product Types
What's the opposite of construc0on?4
locX :: Loc -> Int
locX (Loc x _) = x
locY :: Loc -> Int
locY (Loc _ y) = y
locX (Loc 1 2) -- 1
locY (Loc 1 2) -- 2
4
Deconstruc*on, of course! AKA pa%ern matching.
20. Exercise Set 2
data CharacterStats = CharacterStats Int Int
startingStats :: CharacterStats
healthOf :: CharacterStats -> Int
strengthOf :: CharacterStats -> Int
1. Create a CharacterStats product type to model some character sta3s3cs in an role-playing
game (e.g. health and strength.).
2. Create some values of that type to understand how to use data constructors (startingStats).
3. Use pa@ern matching to extract individual components out of the data type (healthOf,
strengthOf).
21. Coproduct Types
(AKA 'Sum' Types)2
data NPC =
Ogre String Loc Number |
Wolf String Loc Number
2
They get their name from an easy way you can use to compute the size of these sets (hint: sum = addi:on).
22. Coproduct Types
-- The name of
-- the type
-- |
-- |
data NPC =
Ogre String Loc Number |
Wolf String Loc Number
23. Coproduct Types
data NPC =
Ogre String Loc Number |
Wolf String Loc Number
-- |
-- |
-- Data constructor.
24. Coproduct Types
data NPC =
Ogre String Loc Number |
Wolf String Loc Number
-- | | |
-- | /
-- | /
-- Constructor parameters (types).
25. Coproduct Types
Destruc(on / pa/ern matching.
nameOf :: NPC -> String
nameOf (Ogre name _ _) = name
nameOf (Wolf name _ _) = name
26. Coproduct Types
Deconstruc*on / pa/ern matching.
data NPC =
Ogre String Loc Number |
Wolf String Loc Number
nameOf :: NPC -> String
nameOf npc = case npc of
(Ogre name _ _) -> name
(Wolf name _ _) -> name
27. Exercise Set 3
data Monster
= Wolf CharacterStats
| Ogre CharacterStats
bigBadWolf :: CharacterStats
fearfulOgre :: CharacterStats
monsterStrength :: Monster -> Int
1. Create a Monster sum type to represent different types of monsters in a game. Make sure they share at
least one common piece of informa:on (e.g. health or name).
2. Create a few monsters of varying types (bigBadWolf, fearfulOgre).
3. Create a func:on to extract out a piece of informa:on common to all constructors (monsterStrength).
28. Record Types5
data NPC =
Ogre {name :: String, loc :: Loc, health :: Number} |
Wolf {name :: String, loc :: Loc, health :: Number}
5
Record types are represented using na2ve Javascript objects.
29. Record Types
data NPC =
Ogre {name :: String, loc :: Loc, health :: Number} |
Wolf {name :: String, loc :: Loc, health :: Number}
-- | |
-- ----------------------|---------------------/
-- |
-- Record type.
30. Record Types
data NPC =
Ogre {name :: String, loc :: Loc, health :: Number} |
Wolf {name :: String, loc :: Loc, health :: Number}
-- | |
-- --------------------|---------------------/
-- |
-- A 'row' of types.
31. Record Types
data NPC =
Ogre {name :: String, loc :: Loc, health :: Number} |
Wolf {name :: String, loc :: Loc, health :: Number}
-- |
-- A label.
32. Record Types
data NPC =
Ogre {name :: String, loc :: Loc, health :: Number} |
Wolf {name :: String, loc :: Loc, health :: Number}
-- |
-- The type of the label.
33. Record Types
Construc)on / deconstruc)on.
makeWolf :: String -> Loc -> Number -> NPC
makeWolf name loc health = Wolf {name: name, loc: loc, health: health}
nameOf :: NPC -> String
nameOf (Ogre { name : n }) = n
nameOf (Wolf { name : n }) = n
36. Record Types
Magic record syntax stuff.
(_ { name = "Shrek" }) // Function from record to updated record
record { name = _ } // Function from string to updated `record`
(_ { name = _ }) // Guess? :-)
37. Exercise Set 4
type State = {
playerStatus :: CharacterStatus,
playerStats :: CharacterStats }
1. Modify the State record in Game.State to add
playerStatus and playerStats (you will have to modify
initial in the Main module).
2. Modify the describe func9on in Main so it describes the
player state.
40. Exercise Set 5
1. Describe the type of a func4on called defeats, which
determines if a first CharacterStats can be used to defeat a
second CharacterStats (by returning true or false).
2. Implement the func4on by using a lambda (hint: defeats =
stats1 stats2 -> ... or defeats = stats1 ->
stats2 -> ...).
41. Type Aliases
What's in a name?
type CharData =
{name :: String, loc :: Loc, health :: Number}
data NPC = Ogre CharData | Wolf CharData
43. Newtypes
Deconstruc*on / pa/ern matching.
newtype Health = Health Number
isAlive :: Health -> Boolean
isAlive (Health v) = v > 0
isAlive h = case h of
Health v -> v > 0
44. Exercise Set 6
1. Create newtypes for any numeric sta4s4cs in CharacterStats
(e.g. Health and Strength).
2. Create a type synonym called StatsModifier for a func4on
CharacterStats -> CharacterStats.
50. Higher-Order Func/ons
Making sense of "mul0-parameter" func0ons: types.
f :: a -> b -> c -> d -> e
-- f :: (a -> (b -> (c -> (d -> e))))
51. Higher-Order Func/ons
MORE func*ons that return func*ons.
damageNpc :: Number -> (NPC -> NPC)
damageNpc = damage -> npc -> ...
damageNpc :: Number -> (NPC -> NPC)
damageNpc = damage npc -> ...
damageNpc :: Number -> (NPC -> NPC)
damageNpc damage = npc -> ...
damageNpc :: Number -> (NPC -> NPC)
damageNpc damage npc = ...
52. Exercise Set 7
boostHealth :: Int -> (CharacterStats -> CharacterStats)
boostStrength :: Int -> (CharacterStats -> CharacterStats)
1. Create a func-on boostHealth which, given an integer
amount, returns another func-on that takes stats and boosts
their health.
2. Create a func-on boostStrength which, given an integer
amount, returns another func-on that takes stats and boosts
their health.
54. Polymorphic Data
Type constructors: data with "holes".
data Map4x4 a =
Map4x4 a a a a
a a a a
a a a a
a a a a
boolMap4x4 :: Map4x4 Boolean =
Map4x4 true true false true
false true true true
false false false true
true false false true
55. Polymorphic Data
Type-level func-ons.
-- invalid :: Map4x4 <- Not a type; a type function!
valid :: Map4x4 Boolean
The type constructor Map4x4 is a func%on whose
domain is the set of all types. Pass it a type, and it will
return a type!
57. Polymorphic Func/ons
The heart of func-onal abstrac-on.
upperLeft :: forall a. Map4x4 a -> a
upperLeft v _ _ _
_ _ _ _
_ _ _ _
_ _ _ _ = v
58. Polymorphic Func/ons
How to read these crazy signatures.
upperLeft :: forall a. Map4x4 a -> a
-- (a :: Type) -> Map4x4 a -> a
59. Exercise Set 8
data Inventory a b
= LeftHand a
| RightHand b
| BothHands a b
| Empty
isEmpty :: ???
1. Create a polymorphic Inventory sum type that can represents what the
player is carrying in his or her hands.
2. Create a polymorphic func9on that determines whether or not the player is
carrying anything.
61. Extensible Rows
Like duck typing only be1er.
type Point r = { x :: Number, y :: Number | r }
-- | |
-- | |
-- 'remainder' syntax that means "the rest of the row"
gimmeX :: forall r. Point r -> Number
gimmeX p = p.x
gimmeX {x: 1, y: 2, z: 3} -- 1 - works!
-- gimmeX {x: 1, z: 3} -- Invalid, no x!
62. Exercise Set 9
type NonPlayerCharacter = ???
type Item = ???
type PlayerCharacter = ???
getName :: ???
getName r = r.name
1. Create records for NonPlayerCharacter, Item, and
PlayerCharacter that all share at least one field (name).
2. Create a func7on that extracts a name from any record which has at
least a name field of type String.
67. * -> *
Type constructors are just (math) func4ons!
addOne :: Number -> Number
addOne n = n + 1
result = addOne 1
List :: * -> *
data List a = Nil | Cons a (List a)
Result = List Int
68. * -> * -> *
Turtles all the way down.
Map :: * -> * -> *
data Map k v = ...
69. (* -> *) -> *
More turtles.
Container :: (* -> *) -> *
data Container f = {create :: forall a. a -> f a}
list :: Container List
list = Container {create: a -> Cons a Nil}
70. * -> * -> * -> * -> * -> *
Reading type constructors.
foo :: f a b c d e
-- (((((f a) b) c) d) e)
71. !
The name for the category of sets of effects.
foreign import data DOM :: !
72. # !
The name for the category of rows of effects.
-- Supply a row of effects and a type,
-- and get back another type:
foreign import data Eff :: # ! -> * -> *
trace :: forall r. String -> Eff (trace :: Trace | r) Unit
73. # *
The name for the category of rows of types.
-- Supply a row of types, get back another type:
foreign import data Object :: # * -> *
76. FP Toolbox
Maybe it's there, maybe it's not?8
data Maybe a = Nothing | Just a
type Player =
{ armour :: Maybe Armor }
8
AKA null, the FP way.
77. FP Toolbox
List: the ul+mate FP data structure.
data List a = Nil | Cons a (List a)
-- | |
-- head |
-- tail
oneTwoThree = Cons 1 (Cons 2 (Cons 3 Nil))
78. FP Toolbox
Either it's this or it's that.
data Either a b = Left a | Right b
type Player =
{ rightHand :: Either Weapon Shield }
79. FP Toolbox
Tuple, the opposite of Either.9
data Tuple a b = Tuple a b
-- | |
-- first second
I
type Player =
{ wrists :: Tuple (Maybe Bracelet) (Maybe Bracelet) }
9
AKA some)mes it's just too damn hard to name stuff!
81. Exercise Set 10
import Data.List(List(..))
data Location
= OutideCave
| InsideCave
| Plains
data Connection
= GoNorth Location Location
| GoSouth Location Location
| GoEast Location Location
| GoWest Location Location
northsouth :: Location -> Location -> List Connection
northsouth n s = Cons (GoNorth s n) (Cons (GoSouth n s) Nil)
westeast :: Location -> Location -> List Connection
westeast w e = Cons (GoWest e w) (Cons (GoEast w e) Nil)
--
-- OutsideCave -- Plains
-- |
-- |
-- InsideCave
--
gameMap :: List Connection
gameMap =
northsouth OutsideCave InsideCave ++
westeast OutsideCave Plains
1. Define a Location data type to represent all possible loca4ons in the game world.
2. Define a Connection data type to represent a connec4on (or passageway) from one loca4on to another.
3. Create a hypothe4cal gameMap (which has type List Connection), represen4ng the geography of the game world.
83. Type Classes
Generic interfaces in Java.
public interface Appendable<A> {
public A append(A a1, A a2);
}
class AppendableNumber extends Appendable<Float> {
public Float append(Float a1, Float a2) {
return a1 + a2;
}
}
Appendable<Float> appendableNumber = new AppendableNumber();
appendableNumber.append(1, 2); // 3!
84. Type Classes
Generic 'interfaces' in Javascript.
function makeAppendable(append) {
return {
append: append
};
}
var boolAppendable = makeAppendable(
function(v1, v2) {
return v1 && v2;
}
);
boolAppendable.append(true, false); // false!
85. Type Classes
Generic interfaces in PureScript.
class Appendable a where
append :: a -> a -> a
instance appendableNumber :: Appendable Number where
append a1 a2 = a1 + a2
append 1 2 -- 3!
86. Type Classes
Turbocharged polymorphism.
repeat :: forall a. (Appendable a) => Number -> a -> a
repeat 0 a = a
repeat n a = append (repeat (n - 1) a) a
sort :: forall a. (Ord a) => [a] -> [a]
-- etc.
87. Type Classes
Hierarchies: like OO inheritance, but not.
class Eq a where
equals :: a -> a -> Boolean
data Ordering = LT | GT | EQ
class (Eq a) <= Ord a where
compare :: a -> a -> Ordering
88. Type Classes
Hierarchies: like OO inheritance, but not.
class (Eq a) <= Ord a where
-- |
-- |
-- The superclass.
--
-- Read: "Ord a implies Eq a"
89. Exercise Set 11
class Describable a where
describe :: a -> String
examine :: a -> String
data Weapon = Sword | Spear
instance describableWeapon :: Describable Weapon where
describe :: Weapon -> String
examine :: Weapon -> String
1. Define a type class called Describable that can generate small and lengthy (String) descrip7ons of values
of some type.
2. Create a Weapon data type to denote different types of weapons.
3. Create an instance of Describable for the Weapon data type.
96. Scary Sounding Things
The rules of the game.
Rule 1: If something is inside a box, you may change it to anything
else and the result will s9ll be inside the box.
Rule 2: If something is not inside a box, you can pack it into a box.
Rule 3: If something is packed inside a box which is packed inside
another box, you can replace that with a single box containing that
thing.
97. Scary Sounding Things
Your inventory.
Item 1: You have Ripley, a Chihuaha mu2 who can magically change
a lump of coal into a beau:ful present that your friend will like.
Item 2: You have a box containing a box containing a lump of coal.
Which rules should you apply to create a birthday present your
friend will adore???
98. Scary Sounding Things
The rules of the game, redux.
Rule 1: If something is inside a box, you may change it to anything else and
the result will s9ll be inside the box.
(a -> b) -> f a -> f b
Rule 2: If something is not inside a box, you can pack it into a box.
a -> f a
Rule 3: If something is packed inside a box which is packed inside another
box, you can replace that with a single box containing that thing.
f (f a) -> f a
99. Scary Sounding Things
The rules of the game, redux redux.
fmap :: (a -> b) -> f a -> f b -- AKA (<$>)
pure :: a -> f a -- AKA return
join :: f (f a) -> f a
-- bind AKA (>>=) = fa f -> join (fmap f fa)
103. Exercise Set 13
class Evitacilppa f where
erup :: forall a. a -> f a
pa :: forall a b. f (a -> b) -> f a -> f b
1. You are given f Number and Number, for some Evitacilppa f.
If you have a func7on:
add :: Number -> Number -> Number
which "rewrite rules" do you need to use so that you can apply the
add func7on to the two numbers?
105. The Soul of an RPG
Or the types, anyway.
type Game s i = {
initial :: s,
describe :: s -> String,
parse :: String -> Either String i,
update :: s -> i -> Either String s }
runGame :: forall s i. Game s i -> Eff (game :: GAME) Unit
runGame g = ...