I am following the Manning Haskell book to compose functions of robots fighting with lamdas:
-- robot has 3 properties: name/attack/hp
robot (name,attack,hp) = \message -> message (name,attack,hp)
-- getters
name (n,_,_) = n
attack (_,a,_) = a
hp (_,_,hp) = hp
getName aRobot = aRobot name
getAttack aRobot = aRobot attack
getHP aRobot = aRobot hp
-- setters
setName aRobot newName = aRobot (\(n,a,h) -> robot (newName,a,h))
setAttack aRobot newAttack = aRobot (\(n,a,h) -> robot (n,newAttack,h))
setHP aRobot newHP = aRobot (\(n,a,h) -> robot (n,a,newHP))
printRobot aRobot = aRobot (\(n,a,h) -> n ++ " attack:" ++ (show a) ++ " hp:"++ (show h))
fight aRobot1 aRobot2 = setHP aRobot2 (getHP aRobot2 - getAttack aRobot1)
The fight funciton returns a copy of aRobot2(the defender) with deducted HP. Now load the code in GHCi and get this:
*Main> robot1 = robot ("aaa", 20, 100)
*Main> robot2 = robot ("bbb", 15, 120)
*Main> robot2AfterAttack = fight robot1 robot2
<interactive>:36:34: error:
• Occurs check: cannot construct the infinite type:
c1 ~ (([Char], Integer, c1) -> t0) -> t0
Expected type: (([Char], Integer,
(([Char], Integer, c1) -> t0) -> t0)
-> (([Char], Integer, c1) -> t0) -> t0)
-> c1
Actual type: (([Char], Integer,
(([Char], Integer, c1) -> t0) -> t0)
-> c1)
-> c1
• In the second argument of ‘fight’, namely ‘robot2’
In the expression: fight robot1 robot2
In an equation for ‘robot2AfterAttack’:
robot2AfterAttack = fight robot1 robot2
• Relevant bindings include
robot2AfterAttack :: c1 (bound at <interactive>:36:1)
I cannot figure out what goes wrong here.
Let’s try to add type signatures and see if we can work out what is going on:
type RobotInfo = (String,Int,Int)
type Robot = forall a. (RobotInfo -> a) -> a
-- robot has 3 properties: name/attack/hp
robot :: RobotInfo -> Robot
robot (name,attack,hp) = \message -> message (name,attack,hp)
-- getters
name :: RobotInfo -> String
name (n,_,_) = n
attack, hp :: RobotInfo -> Int
attack (_,a,_) = a
hp (_,_,hp) = hp
getName :: Robot -> String
getName aRobot = aRobot name
getAttack, getHP :: Robot -> Int
getAttack aRobot = aRobot attack
getHP aRobot = aRobot hp
-- setters
setName :: Robot -> String -> Robot
setName aRobot newName = aRobot (\(n,a,h) -> robot (newName,a,h))
setAttack, setHP :: Robot -> Int -> Robot
setAttack aRobot newAttack = aRobot (\(n,a,h) -> robot (n,newAttack,h))
setHP aRobot newHP = aRobot (\(n,a,h) -> robot (n,a,newHP))
printRobot :: Robot -> String
printRobot aRobot = aRobot (\(n,a,h) -> n ++ " attack:" ++ (show a) ++ " hp:"++ (show h))
fight :: Robot -> Robot -> Robot
fight aRobot1 aRobot2 = setHP aRobot2 (getHP aRobot2 - getAttack aRobot1)
So we look at the types and see a few things
Robot
is very weird. Why is a robot a function that gives you the state of the robot and then returns whatever you return? Instead, why is a robot not just the state of the robot, which is almost exactly the same but a lot less stupid.fight
it is unclear what this means. I have to read the function to determine that the result is the new state of the second Robot after being hit by the first.What was your type error from?
Well without type signatures Haskell infers some types (which aren’t higher ranked):
robot :: (a,b,c) -> ((a,b,c) -> d) -> d
hp :: (a,b,c) -> c
getAttack :: ((a,b,c) -> b) -> b
getHP :: ((a,b,c) -> c) -> c
setHP :: ((a,b,c) -> ((a,b,g) -> h) -> h) -> g -> ((a,b,g) -> h) -> h
This type already looks crazy but note that Haskell has inferred that setHP
does not take a general robot but rather a specialised robot that can only give you back a new sort-of-robot with whatever you give it. What about when it tries to work out the type of fight
?
fight aRobot1 aRobot2 =
setHP aRobot2 (getHP aRobot2 - getAttack aRobot1)
getAttack
, we infer that aRobot1 :: (x,y,z) -> y
.getHP
we get aRobot2 :: (a,b,c) -> c
-
we get that c~y
(they are the same type) and that Num c
. So we now have aRobot1 :: (x,c,z) -> c
setHP
and that suggests that Robot2 :: (p,q,r) -> ((p,q,c) -> h) -> h
and we need to reconcile these types.p~a
, q~b
, r~c
. Now we need to unify the results: c
from the use of getHP
with (a,b,c) -> h
.c
is the same as (a,b,c) -> h
is the same as (a,b,(a,b,c) -> h) -> h
and so on.I don’t have ghc to hand but I don’t really understand why you didn’t get a type error when you tried to define fight
. (Does anyone else know?)
Anyway here is how I would recommend that you write your program (in a non bizarre weird way):
data Robot = Robot { name :: String, attack :: Int, hp :: Int }
-- no need to define getters because we get them for free and no setters because we don’t use them
robot (name,attack,hp) = Robot name attack hp
instance (Show Robot) where
show (Robot n a h) = n ++ " attack:" ++ (show a) ++ " hp:"++ (show h)
-- fight r1 r2 returns r2 after being attacked by r1
fights robot1 robot2 = robot2 { hp = hp robot2 - attack robot1 }
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments