6

Suppose I have an object which has a point denoting it's location and shape.

type Point = (Double, Double)

data Object =
    Object { location :: Point
           , shape    :: Shape }

where a shape might be like

data Shape
    = Circle { radius :: Double }
    | Rectangle { height :: Double, width :: Double }

Suppose I have a function which can draw objects.

draw :: [Object] -> IO ()

So far here are no problems.

Now, suppose I want objects to move. So objects should have a velocity.

type Velocity = (Double, Double)

data Object =
    Object { location :: (Double, Double)
           , shape    :: Shape
           , velocity :: Velocity }

type Time = Double

move    :: Time ->  Object  ->  Object
moveAll :: Time -> [Object] -> [Object]

The problem is that I need a velocity field to move objects, but I don't need it to draw them.

I think that allowing draw function to know about object's velocity breaks abstraction. Am I right?

How to combine previous object definition with extra velocity field in order to move objects?

  • To use tuples like (Velocity, Object)?
  • To wrap both into new data type? If so, then how to call that data type?

Any suggestions are appreciated.

  • 3
    One option would be to have something like MovableObject that contains Object plus its velocity. Most of the time it's better to have a dedicated, named data type rather than using tuples. Whether or not making velocity available to draw depends on what exactly you want to draw. For example if you decide to draw arrows representing velocity, it would be appropriate. – Petr Pudlák Jul 11 '16 at 14:39
  • 2
    lens provides tools to interact with deeply nested data structures. – Gurkenglas Jul 11 '16 at 16:47
  • 1
    I'd suggest hiding details using a type class, I think that's the cleanest simple solution. It's not a trivial problem, it manifests in widget-based GUIs too. Nevertheless, providing velocities to the rendering system is commonplace as a debugging aid. As an example, in pang-a-lambda (a game I've been working on, I've posted videos online) these are shown by default to make sure collisions work as expected. If you want a reference, I remember reading about this in Jason Gregory's Game Engine Architecture (probably in chapters 10 or 12). – Ivan Perez Jul 12 '16 at 7:43
  • Thanks for comments and answers! I like the type class solution mentioned by @IvanPerez because it allows to list properties which rendering system needs without worrying about how data was designed. I'll accept an answer like that. – wowofbob Jul 15 '16 at 8:10
3

You could declare a typeclass for objects that know their location and shape, and create an instance for Object. Then your draw would accept any instance of that typeclass, but wouldn't be able to find out about any other fields in Object.

| improve this answer | |
5

Besides using tuples or wrapping Object in a new datatype, another option is to parameterize Object with a type variable:

data Object v =
    Object { location :: (Double, Double)
           , shape    :: Shape
           , velocity :: v } deriving (Functor)

When passing an object to the renderer, you could "mute" the velocity by storing a (), using void myObject or perhaps using a polymorphic update like myObject { velocity = () }.

| improve this answer | |
  • 2
    If the renderer has type [Object v] -> IO (), then you know that it cannot use the fact that whenever you call it you actually pass a [Object Velocity], and there is no need for void. – Gurkenglas Jul 11 '16 at 16:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.