10

I've got these data types:

data PointPlus = PointPlus
    { coords :: Point
    , velocity :: Vector
    } deriving (Eq)

data BodyGeo = BodyGeo
    { pointPlus :: PointPlus
    , size :: Point
    } deriving (Eq)

data Body = Body
    { geo :: BodyGeo
    , pict :: Color
    } deriving (Eq)

It's the base datatype for characters, enemies, objects, etc. in my game (well, I just have two rectangles as the player and the ground right now :p).

When a key, the characters moves right, left or jumps by changing its velocity. Moving is done by adding the velocity to the coords. Currently, it's written as follows:

move (PointPlus (x, y) (xi, yi)) = PointPlus (x + xi, y + yi) (xi, yi)

I'm just taking the PointPlus part of my Body and not the entire Body, otherwise it would be:

move (Body (BodyGeo (PointPlus (x, y) (xi, yi)) wh) col) = (Body (BodyGeo (PointPlus (x + xi, y + yi) (xi, yi)) wh) col)

Is the first version of move better? Anyway, if move only changes PointPlus, there must be another function that calls it inside a new Body. I explain: there's a function update which is called to update the game state; it is passed the current game state, a single Body for now, and returns the updated Body.

update (Body (BodyGeo (PointPlus xy (xi, yi)) wh) pict) = (Body (BodyGeo (move (PointPlus xy (xi, yi))) wh) pict)

That tickles me. Everything is kept the same within Body except the PointPlus. Is there a way to avoid this complete "reconstruction" by hand? Like in:

update body = backInBody $ move $ pointPlus body

Without having to define backInBody, of course.

L01man
  • 1,521
  • 10
  • 27

1 Answers1

14

You're looking for "lenses". There are several different packages for lenses; here is a good summary of them.

My understanding is that a lens on a data type a for some field b provides two operations: a way to get the value of b and a way to get a new a with a different value of b. So you would just use a lens to work with the deeply nested PointPlus.

The lens packages provide useful functions for working with lenses as well as ways of generating lenses automatically (with template Haskell) which could be very convenient.

I think they are worth looking into for your project, especially because you are likely to encounter similar problems with nesting in other places thanks to the structure of your data types.

Community
  • 1
  • 1
Tikhon Jelvis
  • 67,485
  • 18
  • 177
  • 214
  • That's perfect, especially with the automatic generation! What about the `move` function? Is it better for it to take the whole `Body` or only its `PointPlus` part? – L01man Jun 10 '12 at 11:53
  • 3
    @L01man I also describe a concrete example very similar to yours in a blog post of mine on lenses [here](http://www.haskellforall.com/2012/01/haskell-for-mainstream-programmers_28.html) – Gabriella Gonzalez Jun 10 '12 at 20:20
  • Your step-by-step explaination helped me understand fully lenses. – L01man Jun 10 '12 at 22:28
  • Don't you think Haskell should implement lenses by default? Types are part of the languages, and record syntax is kind of redundant with lenses but less powerful. – L01man Jun 10 '12 at 22:31