3

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'

3 Answers 3

4

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
Sign up to request clarification or add additional context in comments.

2 Comments

Does this approach mean I can put what are now Moving Bullet and Moving Asteroid into one array/list? That seems wonderful
Yup! You can have a [Movable] now because they are wrapped up into a single type. In the same vein, since Moving is no longer polymorphic, you can have [Moving] that contains both Bullets and Asteroids
3

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

2 Comments

shouldn't be the type of move :: Float -> Moving s -> Moving s?
Thanks for the BTW, but I omitted that to create a minimal working example. I'll go with @Jkeuhlen 's approach for now. But I'll definitely look into this way of solving this and previous problems on which you also suggested me using classes.
2

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

2 Comments

Is one of the ways of implementing this better than the other? (Edit: That probably depends largely on the rest of my implementation of the game)
Yes, that's why I added the last paragraph. If you have a list of all moving objects then jkeuhlen's approach would be better.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.