Haskell foldl cannot construct the infinite type

Space

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:

  1. a binary function
  2. an initial value
  3. 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?

Joseph Sible-Reinstate Monica

Your intuition is right. You have exactly the right idea for how to use foldl. The only problem is that the type you're using for robots is more complicated than Haskell can easily handle. You can use a newtype to make things settle down enough to do that. First, here's the relevant bits of your original code, with type signatures and a type synonym thrown in:

{-# LANGUAGE RankNTypes #-}

type Robot = forall a. ((String, Integer, Integer) -> a) -> a

robot :: (String, Integer, Integer) -> Robot
robot (name,attack,hp)  = \message -> message (name,attack,hp)

damage :: Robot -> Integer -> Robot
damage aRobot attackDamage = aRobot (\(n,a,h) ->
                                      robot (n,a,h-attackDamage))

gentleGiant :: Robot
gentleGiant = robot ("Mr. Friendly", 10, 300)

pummeledGiant :: Robot
pummeledGiant = foldl damage gentleGiant [100,50,100,500]

(Note: The type I used is actually different than the one that was inferred, so the errors from this will be a little bit different, but the root problem is the same.)

See how Robot contains a type variable a? That means it's a polymorphic type. Usually, when you actually go to use a polymorphic type, Haskell can figure out what the type variables will be and fill them in, but in the case of damage, it can't (it's a rank-2 type). That means you're then putting damage, which has a polymorphic type, into foldl, which also has a polymorphic type. Putting one polymorphic type inside of another requires impredicative types, which Haskell doesn't yet support well.

Anyway, to fix it, you can use a newtype wrapper, like this:

{-# LANGUAGE RankNTypes #-}

newtype Robot = MkRobot (forall a. ((String, Integer, Integer) -> a) -> a)

robot :: (String, Integer, Integer) -> Robot
robot (name,attack,hp)  = MkRobot $ \message -> message (name,attack,hp)

damage :: Robot -> Integer -> Robot
damage (MkRobot aRobot) attackDamage = aRobot (\(n,a,h) ->
                                      robot (n,a,h-attackDamage))

gentleGiant :: Robot
gentleGiant = robot ("Mr. Friendly", 10, 300)

pummeledGiant :: Robot
pummeledGiant = foldl damage gentleGiant [100,50,100,500]

Personally, though, I'm rather surprised that a beginner tutorial is using polymorphic continuation-passing style like this, both because it's more complicated than necessary and because it causes problems like this one. I would have designed things completely differently, like this:

data Robot = Robot {
  name :: String,
  attack :: Integer,
  hp :: Integer
}

robot (n,a,h) = Robot n a h

killerRobot = robot ("Kill3r",25,200)

getName = name
getAttack = attack
getHP = hp

setName aRobot newName = aRobot{ name = newName }
setAttack aRobot newAttack = aRobot{ attack = newAttack }
setHP aRobot newHP = aRobot{ hp = newHP }

nicerRobot = setName killerRobot "kitty"
gentlerRobot = setAttack killerRobot 5
softerRobot = setHP killerRobot 50

printRobot (Robot n a h) = n ++
                           " attack:" ++ (show a) ++
                           " hp:"++ (show h)
                                        
damage (Robot n a h) attackDamage = 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

pummeledGiant = foldl damage gentleGiant [100,50,100,500]

That's much more idiomatic Haskell. Note that most of the code is still the same; it just uses a regular type made with data to store the robots, instead of the weird CPS tuple.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Why does foldr work on infinite lists in Haskell but foldl doesn't?

foldl behaviour on infinite lists

Haskell cannot match type

Error on defining Applicative's <*> with record accessors: cannot construct the infinite type: t ~ t -> t1

I'm not sure I understand the type definition of the foldl function in haskell

Fusing multiple foldl' in Haskell

Cannot construct inifnite type

cannot construct the infinite type when using foldl

Find the largest n such that n! < k in Haskell, using foldl and an infinite series

Haskell: cannot construct the infinite type

Haskell - cannot construct the infinite type

Haskell: Understanding the foldl function

Haskell Newbie - Occurs check: cannot construct the infinite type: a ~ [a]

Foldl on a list of functions in Haskell

Struggling to understand a "Cannot construct the infinite type" error

Compile error: Occurs check: cannot construct the infinite type: a1 = [a1]

How can I deal with "Occurs check: cannot construct the infinite type: a0 = [a0]"?

Split Function - cannot construct the infinite type

Haskell, Foldr, and foldl

"infinite type" error in haskell, cannot find whats wrong

haskell fibonacci - cannot construct the infinite type: a0 = [a0]

Haskell foldl implementation with foldr

cannot construct the infinite type: e ~ [e]

Recursion in conditional definition in Haskell gives error ( Occurs check: cannot construct the infinite type:)

Haskell foldl Monad bind

Cannot construct infinite type with functor

Why do I get this "cannot construct the infinite type: a ~ [a]" error in Haskell?

infinite type error reversing a list in Haskell

The problem of foldl (with multi level foldl) in haskell