[Haskell-cafe] Observer pattern in haskell FRP

Nathan Hüsken nathan.huesken at posteo.de
Wed Dec 19 18:22:17 CET 2012


On 12/18/2012 10:52 PM, Heinrich Apfelmus wrote:
> Nathan Hüsken wrote:
>> On 12/08/2012 10:32 AM, Heinrich Apfelmus wrote:
>>> Fair enough, but I don't see how this can be fitted into a general
>>> pattern. If the animation "state" is coupled tightly to the game logic
>>> "state", then the question whether the animation is part of the game
>>> logic or not does not have a clear answer anymore. Hm.
>>
>> I see it like this: The "logic state" may not depend on the "rendering
>> state". If for example the animation of an asteroid changes how or when
>> objects collide with it, than the animation is part of the logic.
>> If however the physics of the game treat the asteroid as a object whose
>> shape does not change depending in the animation, than the animation is
>> part of the "rendering state".
>> So the coupling may only be logic->rendering.
>>
>>> Maybe discussing a concrete example could be very helpful. Could you
>>> give a minimal example that still contains the key difficulties?
>>
>> I put a pseudo C++ example below the mail. I use the terms "model" and
>> "view" for the game logic and rendering respectively.
>> The example is a little different. Asteroids explode when they collide.
>> The moment asteroids explode, they are removed from the model (the game
>> logic) while in the view (rendering) they still exist until the
>> explosion animation is over.
>
> (Sorry for the late reply.)
>
> I see, that's a nice example.
>
> Indeed, if you try to model this situation with dynamic collections
>
>   galaxyModel :: Behavior [AsteroidModel]
>   galaxyView  :: Behavior [AsteroidView]
>
> then you have to keep track of IDs in some way, because a change in 
> the collection of asteroid models needs to be reflected in a 
> corresponding change in the collection of asteroid views.
>
>   identifier :: AsteroidModel -> ID
>
>   eCollisions :: Event [ID]
>   eCollisions = collisions <$> galaxyModel <@ eTick
>       where
>       collisions asteroids = [identifier a | a <- asteroids,
>                               b <- asteroids, a `collides` b]
>
>   galaxyModel = accumB initialAsteroidModels
>               $ removeFromList <$> eCollisions
>
>   galaxyView  = accumB initialAsteroidViews
>               $ startExplosions <$> eCollisions
>
>
> That said, do note that any significant use of pointers in an 
> imperative program translates to the use of identifiers in the purely 
> functional variant. This is very much *independent* of FRP! In other 
> words, if you find that giving certain game objects an "identity" is a 
> good way to structure your code, then you need to use identifiers, 
> regardless of whether you use FRP or not.

Well, direct translation of imperative structures are probably often not 
a smart way to go. Instead a functional approach with the same benefits 
is more desirable. I just find it very hard to think in new directions 
for a problem I already know a solution in an imperative style :).

> Of course, as you note in another message, there are other ways to 
> structure this code. For instance, the second idea would be to use a 
> data type
>
>    type Asteroid = (Maybe AsteroidModel, AsteroidView)
>
> which represents live asteroids as  (Just positionEtc, view)  and dead 
> asteroids as  (Nothing, explosionView) . Then again, one 
> unsatisfactory point about this approach is that an exploding asteroid 
> is now represented explicitly in the game logic as a Nothing  value.
>
>
> A third approach would be to keep an explicit list of explosions.
>
>    data AsteroidModel =
>         AsteroidModel { view :: AsteroidView, pos :: Position }
>    data AsteroidView  =
>         AsteroidView  { rotation :: Angle }
>    data Explosion     =
>         Explosion     { posExp :: Position }
>
>    galaxyView :: Behavior ([AsteroidView], [Explosion])
>    galaxyView = (,) <$> (map view <$> galaxyModel) <$> explosions
>
>    explosions = accumB [] $ startExplosions <$> eCollisions
>
> You do need an event to communicate which asteroids have exploded, but 
> an exploding asteroid will not appear in  galaxyModel anymore. 
> Instead, it will be added as an "anonymous" explosion to the rendering 
> logic. (In a sense, the asteroid views with the state variables  dead 
> = false  and  dead = true  have been split into different types.)
>
>
> I find the third approach to be quite satisfactory. What is your opinion?

I agree, I like it!

> The more I think about this example, the more I think that the 
> underlying difficulty is not FRP, but the use of pointers / identities.
I think the original question where I was asking for replacement pattern 
for observers in FRP is the wrong way to go.
The goal was to separate game logic and rendering logic, and the 
solutions you mention provide this.
Other Problems that are solved with the Observer Pattern probably should 
have a completely different approach in function programming or FRP.

I am very happy with all he replies I got in this thread and I will see 
where it gets me.

Thanks!
Nathan



More information about the Haskell-Cafe mailing list