首页 > 解决方案 > Haskell - 无法构造无限类型

问题描述

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

我无法弄清楚这里出了什么问题。

标签: haskell

解决方案


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

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) -> y
  • 因为getHP我们得到aRobot2 :: (a,b,c) -> c
  • 因为-我们得到了那个c~y(它们是相同的类型)和那个Num c. 所以我们现在有aRobot1 :: (x,c,z) -> c
  • 现在我们有电话setHP,这表明Robot2 :: (p,q,r) -> ((p,q,c) -> h) -> h我们需要协调这些类型。
  • 所以我们匹配第一个参数:p~a, q~b, r~c。现在我们需要统一结果:c从 with 的getHP使用(a,b,c) -> h
  • 所以c是一样的,(a,b,c) -> h是一样的(a,b,(a,b,c) -> h) -> 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 }

推荐阅读