Haskell中的面向对象的功能编程

时间:2018-01-24 15:25:11

标签: haskell

在书中"Get programming with Haskell",我偶然发现了相当混乱的练习。其中一章解释了如何使用闭包创建简单对象。所以,例如,我们有一个元组,它摧毁了一个原始的机器人: (name, attack, hp)。使用这个元组,我们可以构建一个这样的机器人:

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

例如:

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

依此类推,作者解释了如何使用这种结构制作访问器功能:

hp (_,_,hp) = hp
attack (_,a,_) = a

getHP aRobot = aRobot hp
getAttack aRobot = aRobot attack

所以我们可以检查某个机器人有多少生命值:

getHP killerRobot

到目前为止一直很好,我不打算重写整个章节,但我不能再抓到另一件事了。接下来我们有一个功能:

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

它工作得很好。但是当我尝试将它放入一个函数时,它将这些步骤封装并返回最后一步的结果(实际上任务略有不同)我得到了一大堆与类型系统相关的错误。我将展示一个引发错误的简化版本:

roundFights rb1 rb2 = 
 let rb2' = fight rb1 rb2
 in fight rb2' rb1

第二次战斗使编译器爆炸出错。所有这些功能都没有故意的类型签名 - 书中的内容相同 - 因为它只是介绍性章节之一,类型签名尚未解释。

有人可以提出错误吗?

以下是源代码:

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

name (nm, _, _) = nm
attack (_, a, _) = a
hp (_, _, p) = p

getName r = r name
getAttack r = r attack
getHP r = r hp

setName r nm = r $ \(_, a, hp) -> robot (nm, a, hp)
setAttack r a = r $ \(nm, _, hp) -> robot (nm, a, hp)
setHP r hp = r $ \(nm, a, _) -> robot (nm, a, hp)

printRobot r = r $ \(nm, a, hp) -> nm ++ " attack:" ++ show a ++ " hp:" ++ show hp

damage r ad = r $ \(nm, a, hp) -> robot (nm, a, hp - ad)
fight atacker defender = damage defender power where
  power = if getHP atacker > 10
          then getAttack atacker
          else 0

lives = map getHP

roundFights rb1 rb2 = 
   let rb2' = fight rb1 rb2
   in fight rb2' rb1


rb1 = robot("Killer", 25, 200)
rb2 = robot("Slayer", 15, 200)

和我得到的erorrs表:

D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:27:18:
    Occurs check: cannot construct the infinite type:
      t8 ~ ((t7, t8, t8) -> t0) -> t0
    Expected type: ((t7, t8, t8) -> ((t7, t8, t8) -> t0) -> t0) -> t6
      Actual type: ((t7, t8, t8) -> t8) -> t6
    Relevant bindings include
      rb2' :: ((t4, t5, t5) -> t5) -> t8
        (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:26:8)
      rb2 :: ((t2, t3, t6) -> ((t2, t3, t6) -> t) -> t)
             -> ((t4, t5, t5) -> t5) -> t8
        (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:25:17)
      rb1 :: ((t7, t8, t8) -> t8) -> t6
        (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:25:13)
      roundFights :: (((t7, t8, t8) -> t8) -> t6)
                     -> (((t2, t3, t6) -> ((t2, t3, t6) -> t) -> t)
                         -> ((t4, t5, t5) -> t5) -> t8)
                     -> t6
        (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:25:1)
    In the second argument of `fight', namely `rb1'
    In the expression: fight rb2' rb1

D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:30:27:
    No instance for (Num t1) arising from the literal `200'
    The type variable `t1' is ambiguous
    Relevant bindings include
      rb1 :: (([Char], t1, t1) -> t) -> t
        (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:30:1)
    Note: there are several potential instances:
      instance Num Double -- Defined in `GHC.Float'
      instance Num Float -- Defined in `GHC.Float'
      instance Integral a => Num (GHC.Real.Ratio a)
        -- Defined in `GHC.Real'
      ...plus three others
    In the expression: 200
    In the first argument of `robot', namely `("Killer", 25, 200)'
    In the expression: robot ("Killer", 25, 200)

D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:31:27:
    No instance for (Num t1) arising from the literal `200'
    The type variable `t1' is ambiguous
    Relevant bindings include
      rb2 :: (([Char], t1, t1) -> t) -> t
        (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:31:1)
    Note: there are several potential instances:
      instance Num Double -- Defined in `GHC.Float'
      instance Num Float -- Defined in `GHC.Float'
      instance Integral a => Num (GHC.Real.Ratio a)
        -- Defined in `GHC.Real'
      ...plus three others
    In the expression: 200
    In the first argument of `robot', namely `("Slayer", 15, 200)'
    In the expression: robot ("Slayer", 15, 200)

D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:33:8:
    No instance for (Ord t1) arising from a use of `fight'
    The type variable `t1' is ambiguous
    Relevant bindings include
      rb2' :: (([Char], t1, t1) -> t) -> t
        (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:33:1)
    Note: there are several potential instances:
      instance Integral a => Ord (GHC.Real.Ratio a)
        -- Defined in `GHC.Real'
      instance Ord () -- Defined in `GHC.Classes'
      instance (Ord a, Ord b) => Ord (a, b) -- Defined in `GHC.Classes'
      ...plus 24 others
    In the expression: fight rb1 rb2
    In an equation for rb2': rb2' = fight rb1 rb2

D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:34:8:
    No instance for (Ord t1) arising from a use of `fight'
    The type variable `t1' is ambiguous
    Relevant bindings include
      rb1' :: (([Char], t1, t1) -> t) -> t
        (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:34:1)
    Note: there are several potential instances:
      instance Integral a => Ord (GHC.Real.Ratio a)
        -- Defined in `GHC.Real'
      instance Ord () -- Defined in `GHC.Classes'
      instance (Ord a, Ord b) => Ord (a, b) -- Defined in `GHC.Classes'
      ...plus 24 others
    In the expression: fight rb2' rb1
    In an equation for rb1': rb1' = fight rb2' rb1

D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:35:9:
    No instance for (Ord t1) arising from a use of `fight'
    The type variable `t1' is ambiguous
    Relevant bindings include
      rb2'' :: (([Char], t1, t1) -> t) -> t
        (bound at D:\Dropbox\Documents\Work\HS\GetProg\Unit 10\Robot.hs:35:1)
    Note: there are several potential instances:
      instance Integral a => Ord (GHC.Real.Ratio a)
        -- Defined in `GHC.Real'
      instance Ord () -- Defined in `GHC.Classes'
      instance (Ord a, Ord b) => Ord (a, b) -- Defined in `GHC.Classes'
      ...plus 24 others
    In the expression: fight rb1' rb2'
    In an equation for rb2'': rb2'' = fight rb1' rb2' Failed, modules loaded: none.

1 个答案:

答案 0 :(得分:4)

据我所知,您的代码在无类型设置(例如,Scheme或JavaScript)中运行良好。

在类型设置中,它可以工作,但前提是它涉及相当复杂的类型,即rank-2类型。类型推理引擎无法推断那些必须手动注释的那些。

为了强调这一点,让我们尝试仅使用rank-1类型,添加所有注释。这部分编译得很好。

type Robot a = ((String, Int, Int) -> a) -> a

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

name :: (String, Int, Int) -> String
name (nm, _, _) = nm
attack :: (String, Int, Int) -> Int
attack (_, a, _) = a
hp :: (String, Int, Int) -> Int
hp (_, _, p) = p

getName :: Robot String -> String
getName r = r name
getAttack :: Robot Int -> Int
getAttack r = r attack
getHP :: Robot Int -> Int
getHP r = r hp

setName :: Robot (Robot a) -> String -> Robot a
setName r nm = r $ \(_, a, hp) -> robot (nm, a, hp)
setAttack :: Robot (Robot a) -> Int -> Robot a
setAttack r a = r $ \(nm, _, hp) -> robot (nm, a, hp)
setHP :: Robot (Robot a) -> Int -> Robot a
setHP r hp = r $ \(nm, a, _) -> robot (nm, a, hp)

printRobot :: Robot String -> String
printRobot r = r $ \(nm, a, hp) -> nm ++ " attack:" ++ show a ++ " hp:" ++ show hp

damage :: Robot (Robot a) -> Int -> Robot a
damage r ad = r $ \(nm, a, hp) -> robot (nm, a, hp - ad)

fight :: Robot Int -> Robot (Robot a) -> Robot a
fight atacker defender = damage defender power where
  power = if getHP atacker > 10
          then getAttack atacker
          else 0

上面,Robot a表示机器人值,该值只能用于计算a类型的值。例如。从Robot Int您可以提取攻击和HP,但不能提取名称。

查看代码......出现了许多奇怪的类型! fight的类型非常令人费解:

fight :: Robot Int -> Robot (Robot a) -> Robot a

第一个机器人必须产生它的攻击,所以它是Robot Int,而第二个机器人必须战斗并产生Robot a,因此奇怪的类型Robot (Robot a)

由此我们得出结论,我们无法同时输入fight r1 r2fight r2 r1:这需要Int = Robot a,这是不可能的。

  • Couldn't match type ‘Int’ with ‘((String, Int, Int) -> a) -> a’
      Expected type: Robot (Robot a)
        Actual type: Robot Int

可能是什么解决方案?使用rank-2机器人:

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

此处forall a表示排名2机器人可以生成我们选择的任何结果,而不仅仅是单个结果。因此,从rank-2机器人中我们可以提取名称和HP。

我们需要使用构造函数来包装/解包所有内容,这可能有点烦人:

robot :: (String, Int, Int) -> Robot
robot (name, attack, hp) = Robot (\message -> message (name, attack, hp))
getName :: Robot -> String
getName (Robot r) = r name
-- etc.

现在,fight应该有效。我将剩下的剩余时间让OP尝试。

请注意,理论结果(Yoneda的引理)表明我们使用的多态类型forall a. ((String, Int, Int) -> a) -> a(String, Int, Int)是同构的,所以我们确实以更复杂的方式重新构造了元组。

总结:我对Haskell的一本书提出了这种方法感到有些惊讶。对我来说这似乎是非常先进的材料。我想知道目标解决方案是什么。

相关问题