The Coding Wombat
The Coding Wombat

Reputation: 815

Haskell Pattern matching on different data types

I am creating an Asteroids clone and want to create a move function. I thought I could use pattern matching on data types, but of course the type signature then does not conform to the actual method. I want to use different code if the t parameter in the Moving data type in the move function is of data type Bullet and tried this, but that doesn't work. Any ideas besides making a specialised move function (which might be better here, but I still want to know if there are other ways).

So I have Moving Asteroid and Moving Bullet and want to pattern match on the type of either Asteroid or Bullet (or other types which I didn't post here to give a minimum example)

What the move function should do in one sentence: Use wraparound for moving all types of Moving o except Moving Bullet.

Some contextual code:

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

Error:

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

Upvotes: 3

Views: 1879

Answers (3)

leftaroundabout
leftaroundabout

Reputation: 120711

I don't disagree with jkeuhlen (or chepner in your first question on this): you may not really need the type distinction at all, can keep it all on the value level.

But you can also do it on the type level, and this does make some sense because moving an object should never change its type. This is now something where you'd use a class. You could just make move a method:

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

BTW, I think you should probably do something to get rid of the bullets after they've left the screen... perhaps make it move :: Time -> Moving s -> Maybe (Moving s).

Upvotes: 3

Paul Johnson
Paul Johnson

Reputation: 17786

In addition to jkeuhlen's answer, you could also use a typeclass:

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

And similarly for Bullet.

This looks similar to the OO approach of inheritance, which you may be familiar with. However bear in mind that Moveable in this code is a set of types, but is not itself a type. You cannot create a list of Moveable things and put both asteroids and bullets into it. Bullet and Asteroid remain distinct types. If you want to put both of them in a list then you have to use jkeulen's approach (there is nothing wrong with combining both, of course).

Upvotes: 2

jkeuhlen
jkeuhlen

Reputation: 4507

You can't pattern match this way because Moving o is polymorphic. If you had a function that moved only bullets, Moving Bullet would work to pattern match just like this.

There are lots of different ways to get around this.One easy solution, depending on other aspects of your game, would be to bring Bullet and Asteroid into a single Movable data type that you can pattern match on instead:

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

Upvotes: 4

Related Questions