Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I model inheritance in Haskell?

I am attempting to create a game engine that is composed of a few different types:

data Camera = Camera ...
data Light = SpotLight ... | DirectionalLight ...
data Object = Monster ... | Player ... | NPC ...

However, I'm now trying to implement basic physics for all of these entities. This requires that they each contain a pos :: (Double, Double, Double) and a velocity :: (Double, Double, Double).

In object oriented languages, I would implement it as something like:

Camera implements PhysicalObject

where PhysicalObject contains the two properties pos and velocity.

My immediate reaction was to place them all in the same type:

data Object = Monster ... | Player ... | NPC ... | Camera ...

However, I fear that this might make it tough to implement camera-specific functions, light-specific functions, etc. In reality, they have very little else in common other than the fact that they all possess a physical position and velocity in the world.

Is there a simpler way to do this than defining the two properties inside each type constructor?

like image 488
sdasdadas Avatar asked Jan 03 '14 18:01

sdasdadas


2 Answers

I can think of two approaches - type classes and lenses.

Type classes

class PhysicalObject m where
  position :: m -> (Double, Double, Double)
  velocity :: m -> (Double, Double, Double)

You would then make instances for the objects along the following lines

data Camera = Camera 
  { cameraPosition :: (Double,Double,Double)
  , cameraVelocity :: (Double,Double,Double)
  }

instance PhysicalObject Camera where
  position       = cameraPosition
  cameraVelocity = cameraVelocity

and similarly for your other types. Then any function which doesn't need to know the details of an object can just require its arguments to be instances of PhysicalObject, for example:

type TimeInterval = Double

newPosition :: PhysicalObject m => TimeInterval -> m -> (Double,Double,Double)
newPosition dt obj = (x + du * dt, y + dv * dt, z + dw * dt)
 where
  (x,y,z) = position obj
  (u,v,w) = velocity obj

However, you will struggle to write functions which modify your objects using this code - the class tells Haskell how it can access the position and velocity of an object, but not how to modify them.

Lenses

The other option is to turn to the lens library. This is a bit of a beast to being with, but it allows you to write some very natural code. First, there's a bit of boilerplate

{-# LANGUAGE TemplateHaskell #-}
import Control.Lens

Now define some position and velocity data types. Don't worry about the weird field names prefixed with underscores - we won't be using them.

data Pos = Pos { _posX, _posY, _posZ :: Double }
data Vel = Vel { _velX, _velY, _velZ :: Double }

instance Show Pos where show (Pos x y z) = show (x,y,z)
instance Show Vel where show (Vel x y z) = show (x,y,z)

Now you use a bit of Template Haskell to derive lenses for your data types. This will generate type classes HasPos and HasVel whose methods allow you to access and modify any value that is an instance of those classes.

makeClassy ''Pos
makeClassy ''Vel

Now define your camera class, which includes a position and a velocity.

data Camera = Camera
  { _cameraPos :: Pos
  , _cameraVel :: Vel } deriving (Show)

Another bit of Template Haskell will automatically create functions cameraPos and cameraVel that allow you to access and modify the position and velocity of your camera.

makeLenses ''Camera

Finally, declare that your camera is an instance of both the HasPos and HasVel classes, with a default implementation of their methods.

instance HasPos Camera where pos = cameraPos
instance HasVel Camera where vel = cameraVel

Now we're ready to do some real work. Let's define an example camera

camera = Camera (Pos 0 0 0) (Vel 10 5 0)

A function to modify the camera, returning a new one with an updated position, is

move :: (HasPos a, HasVel a) => TimeInterval -> a -> a
move dt obj = obj
  & posX +~ dt * obj^.velX
  & posY +~ dt * obj^.velY
  & posZ +~ dt * obj^.velZ

Note that this is a completely generic function for moving any object that has a position and velocity - it's not at all specific to the Camera type. It also has the advantage of looking a lot like imperative code!

If you now load all this into GHCI, you can see it in action

>> camera
Camera {_cameraPos = (0.0,0.0,0.0), _cameraVel = (10.0,5.0,0.0)}
>> move 0.1 camera
Camera {_cameraPos = (1.0,0.5,0.0), _cameraVel = (10.0,5.0,0.0)}
like image 157
Chris Taylor Avatar answered Sep 22 '22 21:09

Chris Taylor


I would implement it similar to:

type Position = (Double, Double, Double)
type Velocity = (Double, Double, Double)

class PhysicalObject a where
    pos :: a -> Position
    velocity :: a -> Velocity

data Camera = Camera
    { camPos :: Position
    , camVel :: Velocity
    } deriving (Eq, Show)

instance PhysicalObject Camera where
    pos = camPos
    velocity = camVel

Then you can do similarly for each type you define that needs PhysicalObject.

like image 44
bheklilr Avatar answered Sep 24 '22 21:09

bheklilr