Reputation: 815
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
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
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
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