I'm working through the book "Get Programming with Haskell": https://www.manning.com/books/get-programming-with-haskell
There is a lesson introducing OOP in a functional style, using fighting robots as an example:
robot (name,attack,hp) = \message -> message (name,attack,hp)
killerRobot = robot ("Kill3r",25,200)
name (n,_,_) = n
attack (_,a,_) = a
hp (_,_,hp) = hp
getName aRobot = aRobot name
getAttack aRobot = aRobot attack
getHP aRobot = aRobot hp
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))
nicerRobot = setName killerRobot "kitty"
gentlerRobot = setAttack killerRobot 5
softerRobot = setHP killerRobot 50
printRobot aRobot = aRobot (\(n,a,h) -> n ++
" attack:" ++ (show a) ++
" hp:"++ (show h))
damage aRobot attackDamage = aRobot (\(n,a,h) ->
robot (n,a,h-attackDamage))
fight aRobot defender = damage defender attack
where attack = if (getHP aRobot) > 10
then getAttack aRobot
else 0
gentleGiant = robot ("Mr. Friendly", 10, 300)
gentleGiantRound1 = fight killerRobot gentleGiant
killerRobotRound1 = fight gentleGiant killerRobot
gentleGiantRound2 = fight killerRobotRound1 gentleGiantRound1
killerRobotRound2 = fight gentleGiantRound1 killerRobotRound1
gentleGiantRound3 = fight killerRobotRound2 gentleGiantRound2
killerRobotRound3 = fight gentleGiantRound2 killerRobotRound2
fastRobot = robot ("speedy", 15, 40)
slowRobot = robot ("slowpoke",20,30)
fastRobotRound3 = fight slowRobotRound3 fastRobotRound2
fastRobotRound2 = fight slowRobotRound2 fastRobotRound1
fastRobotRound1 = fight slowRobotRound1 fastRobot
slowRobotRound2 = fight fastRobotRound1 slowRobotRound1
slowRobotRound3 = fight fastRobotRound2 slowRobotRound2
slowRobotRound1 = fight fastRobot slowRobot
I saw how the example fights at the bottom of the code had to create new variables to store objects created after each fight, since each object is really just a closure that stores its state and the closure needs to be bound to something.
I was curious to see if I could attack a robot many times in succession using the damage function. The lesson before this introduced higher order functions, including foldl, so I tried to damage the gentleGiant robot multiple times using it:
pummeledGiant = foldl damage gentleGiant [100,50,100,500]
but I get this error:
• Occurs check: cannot construct the infinite type:
t1 ~ (([Char], Integer, Integer) -> t1) -> t1
Expected type: ((([Char], Integer, Integer)
-> (([Char], Integer, Integer) -> t1) -> t1)
-> (([Char], Integer, Integer) -> t1) -> t1)
-> Integer
-> (([Char], Integer, Integer)
-> (([Char], Integer, Integer) -> t1) -> t1)
-> (([Char], Integer, Integer) -> t1)
-> t1
Actual type: ((([Char], Integer, Integer)
-> (([Char], Integer, Integer) -> t1) -> t1)
-> (([Char], Integer, Integer)
-> (([Char], Integer, Integer) -> t1) -> t1)
-> (([Char], Integer, Integer) -> t1)
-> t1)
-> Integer
-> (([Char], Integer, Integer)
-> (([Char], Integer, Integer) -> t1) -> t1)
-> (([Char], Integer, Integer) -> t1)
-> t1
• In the first argument of ‘foldl’, namely ‘damage’
In the expression: foldl damage gentleGiant [100, 50, 100, 500]
In an equation for ‘pummeledGiant’:
pummeledGiant = foldl damage gentleGiant [100, 50, 100, ....]
• Relevant bindings include
pummeledGiant :: (([Char], Integer, Integer)
-> (([Char], Integer, Integer) -> t1) -> t1)
-> (([Char], Integer, Integer) -> t1) -> t1
(bound at <interactive>:2:1)
My understanding is that foldl takes 3 arguments:
- a binary function
- an initial value
- a list of values
and progressively applies the binary function to pairs of values in a left-wise manner, starting with the initial value and the first value in the list of values, "chaining" the result, e.g.:
foldl (+) 0 [1..3] = ((0 + 1) + 2) + 3
I don't understand the error that I encountered and can't see any logical issue with how I used foldl. What have I done wrong?