简体   繁体   English

Haskell foldl无法构造无限类型

[英]Haskell foldl cannot construct the infinite type

I'm working through the book "Get Programming with Haskell": https://www.manning.com/books/get-programming-with-haskell我正在阅读“Get Programming with Haskell”一书: https://www.manning.com/books/get-programming-with-haskell

There is a lesson introducing OOP in a functional style, using fighting robots as an example:有一节课以函数式的方式介绍OOP,以格斗机器人为例:

robot (name,attack,hp)  = \message -> message (name,attack,hp)

killerRobot = robot ("Kill3r",25,200)
name (n,_,_) = n
attack (_,a,_) = a
hp (_,_,hp) = hp 

getName aRobot = aRobot name
getAttack aRobot = aRobot attack
getHP aRobot = aRobot hp


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))

nicerRobot = setName killerRobot "kitty"
gentlerRobot = setAttack killerRobot 5
softerRobot = setHP killerRobot 50

printRobot aRobot = aRobot (\(n,a,h) -> n ++
                                        " attack:" ++ (show a) ++
                                        " hp:"++ (show h))
                                        
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

gentleGiant = robot ("Mr. Friendly", 10, 300)



gentleGiantRound1 = fight killerRobot gentleGiant
killerRobotRound1 = fight gentleGiant killerRobot
gentleGiantRound2 = fight killerRobotRound1 gentleGiantRound1
killerRobotRound2 = fight gentleGiantRound1 killerRobotRound1
gentleGiantRound3 = fight killerRobotRound2 gentleGiantRound2
killerRobotRound3 = fight gentleGiantRound2 killerRobotRound2


fastRobot = robot ("speedy", 15, 40)
slowRobot = robot ("slowpoke",20,30)

fastRobotRound3 = fight slowRobotRound3 fastRobotRound2
fastRobotRound2 = fight slowRobotRound2 fastRobotRound1
fastRobotRound1 = fight slowRobotRound1 fastRobot
slowRobotRound2 = fight fastRobotRound1 slowRobotRound1
slowRobotRound3 = fight fastRobotRound2 slowRobotRound2
slowRobotRound1 = fight fastRobot slowRobot

I saw how the example fights at the bottom of the code had to create new variables to store objects created after each fight, since each object is really just a closure that stores its state and the closure needs to be bound to something.我看到代码底部的战斗示例如何必须创建新变量来存储每次战斗后创建的对象,因为每个 object 实际上只是一个存储其 state 的闭包,并且闭包需要绑定到某些东西。

I was curious to see if I could attack a robot many times in succession using the damage function. The lesson before this introduced higher order functions, including foldl , so I tried to damage the gentleGiant robot multiple times using it:我很好奇我是否可以使用damage function 连续多次攻击机器人。之前的课程介绍了高阶函数,包括foldl ,所以我尝试使用它多次损坏gentleGiant机器人:

pummeledGiant = foldl damage gentleGiant [100,50,100,500]

but I get this error:但我收到此错误:

    • Occurs check: cannot construct the infinite type:
        t1 ~ (([Char], Integer, Integer) -> t1) -> t1
      Expected type: ((([Char], Integer, Integer)
                       -> (([Char], Integer, Integer) -> t1) -> t1)
                      -> (([Char], Integer, Integer) -> t1) -> t1)
                     -> Integer
                     -> (([Char], Integer, Integer)
                         -> (([Char], Integer, Integer) -> t1) -> t1)
                     -> (([Char], Integer, Integer) -> t1)
                     -> t1
        Actual type: ((([Char], Integer, Integer)
                       -> (([Char], Integer, Integer) -> t1) -> t1)
                      -> (([Char], Integer, Integer)
                          -> (([Char], Integer, Integer) -> t1) -> t1)
                      -> (([Char], Integer, Integer) -> t1)
                      -> t1)
                     -> Integer
                     -> (([Char], Integer, Integer)
                         -> (([Char], Integer, Integer) -> t1) -> t1)
                     -> (([Char], Integer, Integer) -> t1)
                     -> t1
    • In the first argument of ‘foldl’, namely ‘damage’
      In the expression: foldl damage gentleGiant [100, 50, 100, 500]
      In an equation for ‘pummeledGiant’:
          pummeledGiant = foldl damage gentleGiant [100, 50, 100, ....]
    • Relevant bindings include
        pummeledGiant :: (([Char], Integer, Integer)
                          -> (([Char], Integer, Integer) -> t1) -> t1)
                         -> (([Char], Integer, Integer) -> t1) -> t1
          (bound at <interactive>:2:1)

My understanding is that foldl takes 3 arguments:我的理解是foldl需要3个arguments:

  1. a binary function二进制 function
  2. an initial value初始值
  3. a list of values值列表

and progressively applies the binary function to pairs of values in a left-wise manner, starting with the initial value and the first value in the list of values, "chaining" the result, eg:并逐步将二进制 function 以左向方式应用于值对,从初始值和值列表中的第一个值开始,“链接”结果,例如:

foldl (+) 0 [1..3] = ((0 + 1) + 2) + 3

I don't understand the error that I encountered and can't see any logical issue with how I used foldl .我不明白我遇到的错误,也看不到我使用foldl的任何逻辑问题。 What have I done wrong?我做错了什么?

Your intuition is right.你的直觉是对的。 You have exactly the right idea for how to use foldl .您对如何使用foldl有完全正确的想法。 The only problem is that the type you're using for robots is more complicated than Haskell can easily handle.唯一的问题是您用于机器人的类型比 Haskell 可以轻松处理的更复杂。 You can use a newtype to make things settle down enough to do that.您可以使用newtype让事情安定下来,足以做到这一点。 First, here's the relevant bits of your original code, with type signatures and a type synonym thrown in:首先,这是原始代码的相关部分,其中包含类型签名和类型同义词:

{-# LANGUAGE RankNTypes #-}

type Robot = forall a. ((String, Integer, Integer) -> a) -> a

robot :: (String, Integer, Integer) -> Robot
robot (name,attack,hp)  = \message -> message (name,attack,hp)

damage :: Robot -> Integer -> Robot
damage aRobot attackDamage = aRobot (\(n,a,h) ->
                                      robot (n,a,h-attackDamage))

gentleGiant :: Robot
gentleGiant = robot ("Mr. Friendly", 10, 300)

pummeledGiant :: Robot
pummeledGiant = foldl damage gentleGiant [100,50,100,500]

(Note: The type I used is actually different than the one that was inferred, so the errors from this will be a little bit different, but the root problem is the same.) (注意:我用的类型其实和推断出来的类型不一样,所以由此产生的错误会有点不同,但根本问题是一样的。)

See how Robot contains a type variable a ?看看Robot如何包含类型变量a That means it's a polymorphic type.这意味着它是一个多态类型。 Usually, when you actually go to use a polymorphic type, Haskell can figure out what the type variables will be and fill them in, but in the case of damage , it can't (it's a rank-2 type).通常,当你实际 go 使用多态类型时, Haskell 可以计算出类型变量是什么并填充它们,但在damage的情况下,它不能(它是 rank-2 类型)。 That means you're then putting damage , which has a polymorphic type, into foldl , which also has a polymorphic type.这意味着您随后将具有多态类型的damage放入也具有多态类型的foldl中。 Putting one polymorphic type inside of another requires impredicative types, which Haskell doesn't yet support well.将一种多态类型置于另一种内部需要非谓语类型,Haskell 还不能很好地支持这种类型。

Anyway, to fix it, you can use a newtype wrapper, like this:无论如何,要修复它,您可以使用一个newtype的包装器,如下所示:

{-# LANGUAGE RankNTypes #-}

newtype Robot = MkRobot (forall a. ((String, Integer, Integer) -> a) -> a)

robot :: (String, Integer, Integer) -> Robot
robot (name,attack,hp)  = MkRobot $ \message -> message (name,attack,hp)

damage :: Robot -> Integer -> Robot
damage (MkRobot aRobot) attackDamage = aRobot (\(n,a,h) ->
                                      robot (n,a,h-attackDamage))

gentleGiant :: Robot
gentleGiant = robot ("Mr. Friendly", 10, 300)

pummeledGiant :: Robot
pummeledGiant = foldl damage gentleGiant [100,50,100,500]

Personally, though, I'm rather surprised that a beginner tutorial is using polymorphic continuation-passing style like this, both because it's more complicated than necessary and because it causes problems like this one.不过,就我个人而言,我对初学者教程使用像这样的多态延续传递样式感到相当惊讶,这既是因为它比必要的更复杂,也是因为它会导致像这样的问题。 I would have designed things completely differently, like this:我会设计完全不同的东西,像这样:

data Robot = Robot {
  name :: String,
  attack :: Integer,
  hp :: Integer
}

robot (n,a,h) = Robot n a h

killerRobot = robot ("Kill3r",25,200)

getName = name
getAttack = attack
getHP = hp

setName aRobot newName = aRobot{ name = newName }
setAttack aRobot newAttack = aRobot{ attack = newAttack }
setHP aRobot newHP = aRobot{ hp = newHP }

nicerRobot = setName killerRobot "kitty"
gentlerRobot = setAttack killerRobot 5
softerRobot = setHP killerRobot 50

printRobot (Robot n a h) = n ++
                           " attack:" ++ (show a) ++
                           " hp:"++ (show h)
                                        
damage (Robot n a h) attackDamage = Robot n a (h-attackDamage)


fight aRobot defender = damage defender attack
  where attack = if (getHP aRobot) > 10
                 then getAttack aRobot
                 else 0

gentleGiant = robot ("Mr. Friendly", 10, 300)



gentleGiantRound1 = fight killerRobot gentleGiant
killerRobotRound1 = fight gentleGiant killerRobot
gentleGiantRound2 = fight killerRobotRound1 gentleGiantRound1
killerRobotRound2 = fight gentleGiantRound1 killerRobotRound1
gentleGiantRound3 = fight killerRobotRound2 gentleGiantRound2
killerRobotRound3 = fight gentleGiantRound2 killerRobotRound2


fastRobot = robot ("speedy", 15, 40)
slowRobot = robot ("slowpoke",20,30)

fastRobotRound3 = fight slowRobotRound3 fastRobotRound2
fastRobotRound2 = fight slowRobotRound2 fastRobotRound1
fastRobotRound1 = fight slowRobotRound1 fastRobot
slowRobotRound2 = fight fastRobotRound1 slowRobotRound1
slowRobotRound3 = fight fastRobotRound2 slowRobotRound2
slowRobotRound1 = fight fastRobot slowRobot

pummeledGiant = foldl damage gentleGiant [100,50,100,500]

That's much more idiomatic Haskell. Note that most of the code is still the same; Haskell 更加地道。请注意,大部分代码仍然相同; it just uses a regular type made with data to store the robots, instead of the weird CPS tuple.它只是使用由data制成的常规类型来存储机器人,而不是奇怪的 CPS 元组。

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

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