简体   繁体   中英

Haskell - cannot construct the infinite type

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

  1. If we give types names then it can be easier to understand what functions are supposed to do
  2. The type 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.
  3. If one looks at the type of 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)
  • Well because of the call to getAttack , we infer that aRobot1 :: (x,y,z) -> y .
  • Because of getHP we get aRobot2 :: (a,b,c) -> c
  • Because of - we get that c~y (they are the same type) and that Num c . So we now have aRobot1 :: (x,c,z) -> c
  • Now we have the call to setHP and that suggests that Robot2 :: (p,q,r) -> ((p,q,c) -> h) -> h and we need to reconcile these types.
  • So we match up the first arguments: p~a , q~b , r~c . Now we need to unify the results: c from the use of getHP with (a,b,c) -> h .
  • So 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 }

This example is taken from Lesson 10 (Unit 1) of Will Kurt's “Get Programming With Haskell”. The task there is to fake the object-oriented programming languages model of “sending messages” to objects with a kind of CPS'ed approach: an object is a function which receives continuation and feeds it with the values of the object's fields:

type ObjectProperties = (Prop1, Prop2, ...)
object_constructor objprops = \message -> message objprops

As it is just first unit of the book, no type features besides functons and tuples are used. So, solving the puzzle with a data type is a bit of cheating.

The code provided by OP is almost just to the source with one exception: the implementation of fight function. In the book it looks like this:

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

(The full source code of the example can be downloaded from the book's webpage .)

The example typechecks and works as expected. I agree that it is a bit uncomfortable to read this without the type annotations, but I think that the idea should be clear.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM