簡體   English   中英

Haskell-無法構造無限類型

[英]Haskell - cannot construct the infinite type

我正在遵循曼寧·哈斯克爾(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)

戰斗功能會返回扣除了HP的aRobot2(防御者)的副本。 現在將代碼加載到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)

我無法弄清楚這里出了什么問題。

讓我們嘗試添加類型簽名,看看我們是否可以解決正在發生的事情:

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~aq~br~c 現在我們需要統一結果: c(a,b,c) -> h一起使用getHP
  • 因此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 }

此示例摘自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

(示例的完整源代碼可以從本書的網頁下載。)

該示例進行檢查並按預期工作。 我同意在沒有類型注釋的情況下閱讀它會有些不舒服,但是我認為這個想法應該很清楚。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM