Upload
john-de-goes
View
978
Download
0
Embed Size (px)
Citation preview
The Easy-Peasy-Lemon-Squeezy, Sta5cally-Typed, Purely Func5onal
Programming Workshop for All!John A. De Goes — @jdegoes
Agenda• Func&ons
• Types, Kinds, & More Func&ons
• FP Toolbox
• OMG COFFEE BREAK!!!
• Type Classes, Effects
• Scary Sounding Things
• Let's Build a Game!
Func%on Defini%on
data Food = Eggs | Coffee
data Happiness = Happy | Neutral | Unhappy
john :: Food -> Happinessjohn Eggs = Unhappyjohn Coffee = Happy
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).
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.
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, $%#@&!!
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).
Product Types
data Loc = Loc Int int-- |-- |-- | -- The name of a function-- that will create values-- of the type. AKA the-- constructor!
Product TypesWhat's the opposite of construc0on?4
locX :: Loc -> IntlocX (Loc x _) = x
locY :: Loc -> IntlocY (Loc _ y) = y
locX (Loc 1 2) -- 1locY (Loc 1 2) -- 2
4 Deconstruc*on, of course! AKA pa%ern matching.
Exercise Set 2data 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).
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).
Coproduct Types
-- The name of-- the type-- |-- | data NPC = Ogre String Loc Number | Wolf String Loc Number
Coproduct Types
data NPC = Ogre String Loc Number | Wolf String Loc Number-- |-- |-- Data constructor.
Coproduct Types
data NPC = Ogre String Loc Number | Wolf String Loc Number-- | | |-- \ | /-- \ | /-- Constructor parameters (types).
Coproduct TypesDestruc(on / pa/ern matching.
nameOf :: NPC -> StringnameOf (Ogre name _ _) = namenameOf (Wolf name _ _) = name
Coproduct TypesDeconstruc*on / pa/ern matching.
data NPC = Ogre String Loc Number | Wolf String Loc Number
nameOf :: NPC -> StringnameOf npc = case npc of (Ogre name _ _) -> name (Wolf name _ _) -> name
Exercise Set 3data 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).
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.
Record Types data NPC = Ogre {name :: String, loc :: Loc, health :: Number} | Wolf {name :: String, loc :: Loc, health :: Number}-- | |-- \----------------------|---------------------/-- |-- Record type.
Record Types data NPC = Ogre {name :: String, loc :: Loc, health :: Number} | Wolf {name :: String, loc :: Loc, health :: Number}-- | |-- \--------------------|---------------------/-- |-- A 'row' of types.
Record Types data NPC = Ogre {name :: String, loc :: Loc, health :: Number} | Wolf {name :: String, loc :: Loc, health :: Number}-- |-- A label.
Record Types data NPC = Ogre {name :: String, loc :: Loc, health :: Number} | Wolf {name :: String, loc :: Loc, health :: Number}-- |-- The type of the label.
Record TypesConstruc)on / deconstruc)on.
makeWolf :: String -> Loc -> Number -> NPCmakeWolf name loc health = Wolf {name: name, loc: loc, health: health}
nameOf :: NPC -> StringnameOf (Ogre { name : n }) = nnameOf (Wolf { name : n }) = n
Record TypesThe dot operator.
nameOf :: NPC -> StringnameOf (Ogre record) = record.namenameOf (Wolf record) = record.name
Record Types'Upda&ng' records.
changeName :: NPC -> NPCchangeName (Ogre r) = Ogre r { name = "Shrek" }changeName (Wolf r) = Wolf r { name = "Big Bad" }
Record TypesMagic record syntax stuff.
(_ { name = "Shrek" }) // Function from record to updated record
record { name = _ } // Function from string to updated `record`
(_ { name = _ }) // Guess? :-)
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.
Basic Func*on Types
data Monster = Giant | Aliendata FavoriteFood = Humans | Kittens
fave :: Monster -> FavoriteFoodfave Giant = Humansfave Alien = Kittens
Basic Func*on TypesLambdas AKA closures AKA anonymous func3ons AKA arrow
func3ons AKA...
fave :: Monster -> FavoriteFoodfave = \monster -> case monster of Giant -> Humans Alien -> Kittens
var fave = function(monster) { ...}
// ECMAScript 6var fave = monster => ...
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 -> ...).
Type AliasesWhat's in a name?
type CharData = {name :: String, loc :: Loc, health :: Number}
data NPC = Ogre CharData | Wolf CharData
NewtypesDeconstruc*on / pa/ern matching.
newtype Health = Health Number
isAlive :: Health -> BooleanisAlive (Health v) = v > 0
isAlive h = case h of Health v -> v > 0
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.
Higher-Order Func/onsFunc%ons that accept func%ons.
likesEmptyString :: (String -> Boolean) -> BooleanlikesEmptyString f = f ""
Higher-Order Func/onsFunc%ons that return func%ons.
matches :: String -> (String -> Boolean)matches v = \text -> text == v
matchesEvil = matches "evil"
matchesEvil "john" -- falsematchesEvil "evil" -- true
Higher-Order Func/ons"Mul%-parameter" func%ons.6
damageNpc :: Number -> (NPC -> NPC)damageNpc damage = \npc -> ...
6 Not really, of course: func/ons in PureScript are always func/ons from one set to another set.
Higher-Order Func/onsMaking sense of "mul0-parameter" func0ons: values.
f a b c d e
-- (((((f a) b) c) d) e)
Higher-Order Func/onsMaking sense of "mul0-parameter" func0ons: types.
f :: a -> b -> c -> d -> e
-- f :: (a -> (b -> (c -> (d -> e))))
Higher-Order Func/onsMORE 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 = ...
Exercise Set 7boostHealth :: 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.
Polymorphic DataType 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
Polymorphic DataType-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!
Polymorphic Func/onsThe heart of func-onal abstrac-on.
upperLeft :: forall a. Map4x4 a -> aupperLeft v _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ = v
Polymorphic Func/onsHow to read these crazy signatures.
upperLeft :: forall a. Map4x4 a -> a
-- (a :: Type) -> Map4x4 a -> a
Exercise Set 8data 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.
Extensible RowsLike 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 -> NumbergimmeX p = p.x
gimmeX {x: 1, y: 2, z: 3} -- 1 - works!
-- gimmeX {x: 1, z: 3} -- Invalid, no x!
Exercise Set 9type 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.
*The name for the category of sets of values.
(AKA Type)
Includes things like:
• CharacterStatus
• CharacterStats
• String
* -> *Type constructors are just (math) func4ons!
addOne :: Number -> NumberaddOne n = n + 1
result = addOne 1
List :: * -> *data List a = Nil | Cons a (List a)
Result = List Int
(* -> *) -> *More turtles.
Container :: (* -> *) -> *data Container f = {create :: forall a. a -> f a}
list :: Container Listlist = Container {create: \a -> Cons a Nil}
# !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
# *The name for the category of rows of types.
-- Supply a row of types, get back another type:foreign import data Object :: # * -> *
FP ToolboxMaybe 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.
FP ToolboxList: the ul+mate FP data structure.
data List a = Nil | Cons a (List a)-- | |-- head |-- tail
oneTwoThree = Cons 1 (Cons 2 (Cons 3 Nil))
FP ToolboxEither it's this or it's that.
data Either a b = Left a | Right b
type Player = { rightHand :: Either Weapon Shield }
FP ToolboxTuple, the opposite of Either.9
data Tuple a b = Tuple a b-- | |-- first secondItype Player = { wrists :: Tuple (Maybe Bracelet) (Maybe Bracelet) }
9 AKA some)mes it's just too damn hard to name stuff!
Exercise Set 10import 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 Connectionnorthsouth n s = Cons (GoNorth s n) (Cons (GoSouth n s) Nil)
westeast :: Location -> Location -> List Connectionwesteast w e = Cons (GoWest e w) (Cons (GoEast w e) Nil)
---- OutsideCave -- Plains-- |-- |-- InsideCave--gameMap :: List ConnectiongameMap = 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.
Type ClassesGeneric 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!
Type ClassesGeneric 'interfaces' in Javascript.
function makeAppendable(append) { return { append: append };}
var boolAppendable = makeAppendable( function(v1, v2) { return v1 && v2; });
boolAppendable.append(true, false); // false!
Type ClassesGeneric 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!
Type ClassesTurbocharged polymorphism.
repeat :: forall a. (Appendable a) => Number -> a -> arepeat 0 a = arepeat n a = append (repeat (n - 1) a) a
sort :: forall a. (Ord a) => [a] -> [a]
-- etc.
Type ClassesHierarchies: 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
Type ClassesHierarchies: like OO inheritance, but not.
class (Eq a) <= Ord a where-- |-- |-- The superclass.---- Read: "Ord a implies Eq a"
Exercise Set 11class 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.
Scary Sounding ThingsThe 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.
Scary Sounding ThingsYour 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???
Scary Sounding ThingsThe 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
Scary Sounding ThingsThe 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)
Nah, just kiddingScary sounding things give you rewrite rules you can use to manipulate the types into the
form you require.
Exercise Set 13class 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?
The Soul of an RPGOr 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) UnitrunGame g = ...