【问题标题】:Haskell - cannot construct the infinite typeHaskell - 无法构造无限类型
【发布时间】:2018-10-27 16:14:34
【问题描述】:

我正在按照 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)

战斗功能返回aRobot2(防御者)的副本并扣除HP。 现在在 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)

我不知道这里出了什么问题。

【问题讨论】:

  • 提示:写之前写出你要写的函数的类型。
  • 我只能通过明确地给 fight 一个 rank-2 类型来使这段代码工作。代码有效,但 Haskell 无法自动为其派生类型。
  • @melpomene 你如何给“战斗”一个 2 级类型?我试过了,但 GHC 仍然抱怨一些类型错误。
  • @user1285245 查看 Dan Robertson 的回答,type Robot = forall a. (RobotInfo -&gt; a) -&gt; a

标签: haskell


【解决方案1】:

让我们尝试添加类型签名,看看我们是否可以弄清楚发生了什么:

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)

所以我们看看类型,看看一些东西

  1. 如果我们给类型命名,那么可以更容易理解函数应该做什么
  2. Robot 类型很奇怪。为什么机器人是一个函数,它给你机器人的状态,然后返回你返回的任何东西?相反,为什么机器人不只是机器人的状态,几乎完全一样,但不那么愚蠢。
  3. 如果查看fight 的类型,则不清楚这意味着什么。我必须阅读该函数以确定结果是第二个机器人在被第一个机器人击中后的新状态。

您的类型错误来自什么?

如果没有类型签名,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

这种类型已经看起来很疯狂,但请注意,Haskell 推断setHP 不采用通用机器人,而是采用专用机器人,它只能用你给它的任何东西给你一种新的机器人。当它试图计算出fight 的类型时呢?

fight aRobot1 aRobot2 =
  setHP aRobot2 (getHP aRobot2 - getAttack aRobot1)
  • 由于调用了getAttack,我们推断aRobot1 :: (x,y,z) -&gt; y
  • 因为getHP 我们得到aRobot2 :: (a,b,c) -&gt; c
  • 由于-,我们得到c~y(它们是同一类型)和Num c。所以我们现在有aRobot1 :: (x,c,z) -&gt; c
  • 现在我们调用了setHP,这表明Robot2 :: (p,q,r) -&gt; ((p,q,c) -&gt; h) -&gt; h,我们需要协调这些类型。
  • 所以我们匹配第一个参数:p~aq~br~c。现在我们需要将结果统一起来:c 来自getHP 的使用与(a,b,c) -&gt; h
  • 所以c(a,b,c) -&gt; h 相同与(a,b,(a,b,c) -&gt; h) -&gt; h 相同等等。

我手头没有 ghc,但我真的不明白为什么您在尝试定义 fight 时没有收到类型错误。 (有人知道吗?)

无论如何,我建议您编写程序的方式如下(以一种不奇怪的怪异方式):

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 }

【讨论】:

    【解决方案2】:

    此示例取自 Will Kurt 的“使用 Haskell 进行编程”的第 10 课(第 1 单元)。那里的任务是用一种 CPS 的方法来伪造面向对象的编程语言模型“发送消息”给对象:对象是一个函数,它接收延续并将对象字段的值提供给它:

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

    因为它只是本书的第一单元,所以除了函数和元组之外没有使用任何类型特征。所以,用数据类型解决这个难题有点作弊。

    OP 提供的代码几乎只针对源代码,只有一个例外:fight 函数的实现。在书中它是这样的:

    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
    

    (示例的完整源代码可以从book's webpage下载。)

    示例类型检查并按预期工作。我同意没有类型注释的情况下阅读这个有点不舒服,但我认为这个想法应该很清楚。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多