- Functions
- Types, Kinds, & More Functions
- FP Toolbox
- OMG COFFEE BREAK!!!
- Type Classes, Effects
- Scary Sounding Things
- Let's Build a Game!
data Food = Eggs | Coffee
data Happiness = Happy | Neutral | Unhappy
john :: Food -> Happiness
john Eggs = Unhappy
john Coffee = Happy
> john Eggs
Unhappy
> john Coffee
Happy
- Totality. Every element in domain must be mapped to some element in codomain.
- Determinism. Applying a function with the same value (in domain) results in same value (in codomain).
data CharacterStatus = Content | Hungry | Tired
describeCharacterStatus :: CharacterStatus -> String
- Create a set called
CharacterStatus
, which represents the different states of the player. - Create a function called
describeCharacterStatus
which describes the state of the player.
String
: The set that contains all strings;"foo"
is an element of this set.Number
: The set that contains all numbers;15.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 valuestrue
andfalse
.
Plus records {foo: "bar"}
and arrays [1, 2, 3]
!
Product Types2
data Loc = Loc Int Int
data Loc = Loc Int Int
-- |
-- |
-- |
-- The name of
-- the type.
data Loc = Loc Int Int
-- |
-- |
-- |
-- Nonsense, please ignore!
data Loc = Loc Int int
-- |
-- |
-- |
-- The name of a function
-- that will create values
-- of the type. AKA the
-- constructor!
data Loc = Loc Int Int
-- \ /
-- \/
-- Constructor parameters (types).
data Loc = Loc Int Int
whereAmI = Loc 1 2
What's the opposite of construction?3
locX :: Loc -> Int
locX (Loc x _) = x
locY :: Loc -> Int
locY (Loc _ y) = y
locX (Loc 1 2) -- 1
locY (Loc 1 2) -- 2
locX :: Loc -> Int
locX l = case l of
(Loc x _) -> x
data CharacterStats = CharacterStats Int Int
startingStats :: CharacterStats
healthOf :: CharacterStats -> Int
strengthOf :: CharacterStats -> Int
- Create a
CharacterStats
product type to model some character statistics in an role-playing game (e.g. health and strength.). - Create some values of that type to understand how to use data constructors (
startingStats
). - Use pattern matching to extract individual components out of the data type (
healthOf
,strengthOf
).
(AKA 'Sum' Types)4
data NPC =
Ogre String Loc Number |
Wolf String Loc Number
-- The name of
-- the type
-- |
-- |
data NPC =
Ogre String Loc Number |
Wolf String Loc Number
data NPC =
Ogre String Loc Number |
Wolf String Loc Number
-- |
-- |
-- Data constructor.
data NPC =
Ogre String Loc Number |
Wolf String Loc Number
-- | | |
-- \ | /
-- \ | /
-- Constructor parameters (types).
nameOf :: NPC -> String
nameOf (Ogre name _ _) = name
nameOf (Wolf name _ _) = name
data NPC =
Ogre String Loc Number |
Wolf String Loc Number
nameOf :: NPC -> String
nameOf npc = case npc of
(Ogre name _ _) -> name
(Wolf name _ _) -> name
data Monster
= Wolf CharacterStats
| Ogre CharacterStats
bigBadWolf :: CharacterStats
fearfulOgre :: CharacterStats
monsterStrength :: Monster -> Int
- Create a
Monster
sum type to represent different types of monsters in a game. Make sure they share at least one common piece of information (e.g.health
orname
). - Create a few monsters of varying types (
bigBadWolf
,fearfulOgre
). - Create a function to extract out a piece of information common to all constructors (
monsterStrength
).
Record Types5
data NPC =
Ogre {name :: String, loc :: Loc, health :: Number} |
Wolf {name :: String, loc :: Loc, health :: Number}
data NPC =
Ogre {name :: String, loc :: Loc, health :: Number} |
Wolf {name :: String, loc :: Loc, health :: Number}
-- | |
-- \----------------------|---------------------/
-- |
-- Record type.
data NPC =
Ogre {name :: String, loc :: Loc, health :: Number} |
Wolf {name :: String, loc :: Loc, health :: Number}
-- | |
-- \--------------------|---------------------/
-- |
-- A 'row' of types.
data NPC =
Ogre {name :: String, loc :: Loc, health :: Number} |
Wolf {name :: String, loc :: Loc, health :: Number}
-- |
-- A label.
data NPC =
Ogre {name :: String, loc :: Loc, health :: Number} |
Wolf {name :: String, loc :: Loc, health :: Number}
-- |
-- The type of the label.
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
nameOf :: NPC -> String
nameOf (Ogre record) = record.name
nameOf (Wolf record) = record.name
changeName :: NPC -> NPC
changeName (Ogre r) = Ogre r { name = "Shrek" }
changeName (Wolf r) = Wolf r { name = "Big Bad" }
(_ { name = "Shrek" }) // Function from record to updated record
record { name = _ } // Function from string to updated `record`
(_ { name = _ }) // Guess? :-)
type State = {
playerStatus :: CharacterStatus,
playerStats :: CharacterStats }
- Modify the
State
record inGame.State
to addplayerStatus
andplayerStats
(you will have to modifyinitial
in theMain
module). - Modify the
describe
function inMain
so it describes the player state.
data Monster = Giant | Alien
data FavoriteFood = Humans | Kittens
fave :: Monster -> FavoriteFood
fave Giant = Humans
fave Alien = Kittens
fave :: Monster -> FavoriteFood
fave = \monster -> case monster of
Giant -> Humans
Alien -> Kittens
var fave = function(monster) {
...
}
// ECMAScript 6
var fave = monster => ...
- Describe the type of a function called
defeats
, which determines if a firstCharacterStats
can be used to defeat a secondCharacterStats
(by returningtrue
orfalse
). - Implement the function by using a lambda (hint:
defeats = \stats1 stats2 -> ...
ordefeats = \stats1 -> \stats2 -> ...
).
type CharData =
{name :: String, loc :: Loc, health :: Number}
data NPC = Ogre CharData | Wolf CharData
newtype Health = Health Number
dead :: Health
dead = Health 0
newtype Health = Health Number
isAlive :: Health -> Boolean
isAlive (Health v) = v > 0
isAlive h = case h of
Health v -> v > 0
- Create newtypes for any numeric statistics in
CharacterStats
(e.g.Health
andStrength
). - Create a type synonym called
StatsModifier
for a functionCharacterStats -> CharacterStats
.
likesEmptyString :: (String -> Boolean) -> Boolean
likesEmptyString f = f ""
matches :: String -> (String -> Boolean)
matches v = \text -> text == v
matchesEvil = matches "evil"
matchesEvil "john" -- false
matchesEvil "evil" -- true
"Multi-parameter" functions.6
damageNpc :: Number -> (NPC -> NPC)
damageNpc damage = \npc -> ...
f a b c d e
-- (((((f a) b) c) d) e)
f :: a -> b -> c -> d -> e
-- f :: (a -> (b -> (c -> (d -> e))))
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 = ...
boostHealth :: Int -> (CharacterStats -> CharacterStats)
boostStrength :: Int -> (CharacterStats -> CharacterStats)
- Create a function
boostHealth
which, given an integer amount, returns another function that takes stats and boosts their health. - Create a function
boostStrength
which, given an integer amount, returns another function that takes stats and boosts their health.
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
-- invalid :: Map4x4 <- Not a type; a type function!
valid :: Map4x4 Boolean
The type constructor Map4x4
is a function whose domain is the set of all types. Pass it a type, and it will return a type!
upperLeft :: forall a. Map4x4 a -> a
upperLeft v _ _ _
_ _ _ _
_ _ _ _
_ _ _ _ = v
upperLeft :: forall a. Map4x4 a -> a
-- (a :: Type) -> Map4x4 a -> a
data Inventory a b
= LeftHand a
| RightHand b
| BothHands a b
| Empty
isEmpty :: ???
- Create a polymorphic
Inventory
sum type that can represents what the player is carrying in his or her hands. - Create a polymorphic function that determines whether or not the player is carrying anything.
type Point r = { x :: Number, y :: Number | r }
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!
type NonPlayerCharacter = ???
type Item = ???
type PlayerCharacter = ???
getName :: ???
getName r = r.name
- Create records for
NonPlayerCharacter
,Item
, andPlayerCharacter
that all share at least one field (name
). - Create a function that extracts a name from any record which has at least a
name
field of typeString
.
Includes things like:
CharacterStatus
CharacterStats
String
data List a = Nil | Cons a (List a)
addOne :: Number -> Number
addOne n = n + 1
result = addOne 1
List :: * -> *
data List a = Nil | Cons a (List a)
Result = List Int
Map :: * -> * -> *
data Map k v = ...
Container :: (* -> *) -> *
data Container f = {create :: forall a. a -> f a}
list :: Container List
list = Container {create: \a -> Cons a Nil}
foo :: f a b c d e
-- (((((f a) b) c) d) e)
foreign import data DOM :: !
-- 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
-- Supply a row of types, get back another type:
foreign import data Object :: # * -> *
Foreign Types7
foreign import data jQuery :: *
Maybe it's there, maybe it's not?8
data Maybe a = Nothing | Just a
type Player =
{ armour :: Maybe Armor }
data List a = Nil | Cons a (List a)
-- | |
-- head |
-- tail
oneTwoThree = Cons 1 (Cons 2 (Cons 3 Nil))
data Either a b = Left a | Right b
type Player =
{ rightHand :: Either Weapon Shield }
Tuple
, the opposite of Either
.9
data Tuple a b = Tuple a b
-- | |
-- first second
I
type Player =
{ wrists :: Tuple (Maybe Bracelet) (Maybe Bracelet) }
[1, 2, 3] :: Array Number
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
- Define a
Location
data type to represent all possible locations in the game world. - Define a
Connection
data type to represent a connection (or passageway) from one location to another. - Create a hypothetical
gameMap
(which has typeList Connection
), representing the geography of the game world.
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!
function makeAppendable(append) {
return {
append: append
};
}
var boolAppendable = makeAppendable(
function(v1, v2) {
return v1 && v2;
}
);
boolAppendable.append(true, false); // false!
class Appendable a where
append :: a -> a -> a
instance appendableNumber :: Appendable Number where
append a1 a2 = a1 + a2
append 1 2 -- 3!
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.
class Eq a where
equals :: a -> a -> Boolean
data Ordering = LT | GT | EQ
class (Eq a) <= Ord a where
compare :: a -> a -> Ordering
class (Eq a) <= Ord a where
-- |
-- |
-- The superclass.
--
-- Read: "Ord a implies Eq a"
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
- Define a type class called
Describable
that can generate small and lengthy (String
) descriptions of values of some type. - Create a
Weapon
data type to denote different types of weapons. - Create an instance of
Describable
for theWeapon
data type.
import Debug.Trace
main = trace "Hello World!"
import Debug.Trace
main = do
trace "Hello World!"
trace "Bye World!"
- Study the
Main
module to see how it uses effects to print out a description.
Rule 1: If something is inside a box, you may change it to anything else and the result will still 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.
Item 1: You have Ripley, a Chihuaha mutt who can magically change a lump of coal into a beautiful 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???
Rule 1: If something is inside a box, you may change it to anything else and the result will still 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
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)
Scary sounding things give you rewrite rules you can use to manipulate the types into the form you require.
class Evitacilppa f where
erup :: forall a. a -> f a
pa :: forall a b. f (a -> b) -> f a -> f b
-
You are given
f Number
andNumber
, for someEvitacilppa f
. If you have a function:add :: Number -> Number -> Number
which "rewrite rules" do you need to use so that you can apply the
add
function to the two numbers?
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 = ...
Footnotes
-
Not really, $%#@&!! ↩
-
They get their name from an easy way you can use to compute the size of these sets (hint: product = multiplication). ↩
-
Deconstruction, of course! AKA pattern matching. ↩
-
They get their name from an easy way you can use to compute the size of these sets (hint: sum = addition). ↩
-
Record types are represented using native Javascript objects. ↩
-
Not really, of course: functions in PureScript are always functions from one set to another set. ↩
-
THERE BE DRAGONZ HERE!!! ↩
-
AKA
null
, the FP way. ↩ -
AKA sometimes it's just too damn hard to name stuff! ↩