Haskell模式匹配不同的数据类型

时间:2018-10-23 12:38:35

标签: haskell data-structures pattern-matching

我正在创建一个Asteroids克隆,并希望创建一个移动功能。我以为可以在数据类型上使用模式匹配,但是类型签名当然不符合实际方法。 如果t函数中Moving数据类型中的move参数是Bullet数据类型,我想使用不同的代码,但是尝试了。除了制作专门的移动功能外,还有其他想法(在这里可能会更好,但是我仍然想知道是否还有其他方法)。

因此,我有Moving AsteroidMoving Bullet,并且想对AsteroidBullet的类型(或其他我未在此处发布的类型)进行模式匹配举个最小的例子)

move函数应该用一句话来做:使用环绕式移动Moving o以外的所有类型的Moving Bullet

一些上下文代码:

data Moving s = Moving {
                    position :: Position,
                    velocity :: Velocity,
                    size :: Float, 
                    specifics :: s
}

data Bullet = Bullet {
                damage :: Int
              }
            | DeadBullet

data Asteroid = Asteroid  
              | DeadAsteroid

move :: Float -> Moving o -> Moving o
move secs (Moving (x, y) v@(vx, vy) s t@(Bullet _)) = Moving (x', y') v s t
  where x' = (x + vx * secs)
        y' = (y + vy * secs)

move secs (Moving (x, y) v@(vx, vy) s t) = Moving (x', y') v s t
  where x' = (x + vx * secs) `mod'` width
        y' = (y + vy * secs) `mod'` height

错误:

src\Controller.hs:100:42: error:
    * Couldn't match expected type `o' with actual type `Bullet'

3 个答案:

答案 0 :(得分:3)

您无法通过模式匹配,因为Moving o是多态的。如果您有一个仅移动项目符号的功能,则Moving Bullet可以像这样来进行模式匹配。

有许多不同的方法可以解决此问题。根据您游戏的其他方面,一个简单的解决方案是将BulletAsteroid整合到单个Movable数据中可以用来进行模式匹配的类型:

data Moving = Moving {
                    position :: Position,
                    velocity :: Velocity,
                    size :: Float, 
                    specifics :: Movable
}

data Movable = B Bullet | A Asteroid 

data Bullet = Bullet {
                damage :: Int
              }
            | DeadBullet

data Asteroid = Asteroid  
              | DeadAsteroid 

move :: Float -> Moving -> Moving
move secs (Moving (x, y) v@(vx, vy) s t@(B _)) = Moving (x', y') v s t
  where x' = (x + vx * secs)
        y' = (y + vy * secs)

move secs (Moving (x, y) v@(vx, vy) s t) = Moving (x', y') v s t
  where x' = (x + vx * secs) `mod'` width
        y' = (y + vy * secs) `mod'` height

答案 1 :(得分:2)

我完全不同意jkeuhlen(或chepner in your first question on this):您可能根本不需要类型区分,可以将其全部保留在值级别。

但是您也可以在类型级别上执行此操作,这确实有些道理,因为移动对象绝不能更改其类型。现在,您可以在其中使用课程。您可以将move用作方法:

type Time = Float

class ObjSpecific s where
  move :: Time -> Moving s -> Moving s

instance ObjSpecific Bullet where
  move δt (Moving p v s t) = -- definition without edge-wrapping
instance ObjSpecific Asteroid where
  move δt (...) = ... -- definition with edge-wrapping

顺便说一句,我想您应该在子弹离开屏幕后采取一些措施来摆脱子弹...也许把它变成move :: Time -> Moving s -> Maybe (Moving s)

答案 2 :(得分:2)

除了jkeuhlen的答案之外,您还可以使用类型类:

class Moveable a where
    move :: Float -> a -> a
    position :: a -> Position
    velocity :: a -> Velocity

data Asteroid = Asteroid {
      asteroidP :: Position,
      asteroidV :: Velocity
   }

instance Moveable Asteroid where
    move secs (Asteroid (x, y) v@(vx, vy)) = 
       Asteroid ((x + secs*vx) `mod'` width, (y + secs*vy) `mod'` height) v
    position = asteroidP
    velocity = asteroidV

Bullet也是如此。

这看起来类似于您可能熟悉的OO继承方法。但是请记住,此代码中的Moveable是一组类型,但本身不是类型。您不能创建可移动物体的列表,也不能将小行星和子弹都放入其中。子弹和小行星仍然是不同的类型。如果要将它们都放在列表中,则必须使用jkeulen的方法(将两者组合在一起当然没有什么问题。)