简体   繁体   English

Haskell-无法构造无限类型

[英]Haskell - cannot construct the infinite type

I am following the Manning Haskell book to compose functions of robots fighting with lamdas: 我正在遵循曼宁·哈斯克尔(Manning Haskell)的书,以构成与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. 战斗功能会返回扣除了HP的aRobot2(防御者)的副本。 Now load the code in GHCi and get this: 现在将代码加载到GHCi中并获得以下代码:

*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. Robot类型很奇怪。 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. 如果人们看一下fight的类型,还不清楚这意味着什么。 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): 如果没有类型签名,Haskell会推断出一些类型(排名不高):

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. 这种类型已经看起来很疯狂,但请注意,Haskell推断setHP不会使用通用机器人,而是使用专用机器人,它只能将您提供的一切带给您新的机器人。 What about when it tries to work out the type of fight ? 当它尝试确定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 . 由于对getAttack的调用,我们推断aRobot1 :: (x,y,z) -> y
  • Because of getHP we get aRobot2 :: (a,b,c) -> c 由于使用了getHP我们得到了aRobot2 :: (a,b,c) -> c
  • Because of - we get that c~y (they are the same type) and that Num c . 因为-我们得到c~y (它们是相同的类型)和Num c So we now have aRobot1 :: (x,c,z) -> c 所以我们现在有了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. 现在我们有了对setHP的调用,这表明Robot2 :: (p,q,r) -> ((p,q,c) -> h) -> h ,我们需要协调这些类型。
  • So we match up the first arguments: p~a , q~b , r~c . 因此,我们匹配了第一个参数: p~aq~br~c Now we need to unify the results: c from the use of getHP with (a,b,c) -> h . 现在我们需要统一结果: c(a,b,c) -> h一起使用getHP
  • So c is the same as (a,b,c) -> h is the same as (a,b,(a,b,c) -> h) -> h and so on. 因此c等于(a,b,c) -> h(a,b,(a,b,c) -> h) -> h ,依此类推。

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 . 我手头没有ghc,但我不太了解为什么在尝试定义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”. 此示例摘自Will Kurt的“使用Haskell编程”的第10课(第1单元)。 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: 那里的任务是使用一种CPS的方法来伪造“向对象发送消息”的面向对象的编程语言模型:对象是一个函数,它接收连续性并将其馈送给对象字段的值:

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. OP提供的代码几乎只针对源代码,但有一个例外: fight函数的实现。 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. 我同意在没有类型注释的情况下阅读它会有些不舒服,但是我认为这个想法应该很清楚。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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