Adventures in Existential Quantification

I recently found this post about dealing with Renderable objects in the context of game design. The solution presented there introduced a composite type

data Game = Game Ball Player Player

where Ball and Player are instances of a typeclass Render a:

class Render a
where render :: a -> IO ()

Then one can write a function that renders a Game by pattern matching on the Game constructor. If the constructor for Games changes, so must the Render implementation for Game. This is not ideal, so one might try to use a composite pattern like

data ExtendedGame = ExtendedGame Game Score 

to extend Game, but then functions that consumed Games cannot automatically consume ExtendedGames even if they only require Game functionality. Creating dependencies on constructors like this seems like a bad idea.

This approach to rendering was introduced because otherwise one would have to deal with rendering heterogeneous lists like [Ball, Player, Player].

ghci -XExistentialQuantification
> class Render a where render :: a -> IO () 
> data Player = Player
> instance Render Player where render p = print "player"
> data Ball = Ball
> instance Render Ball where render b = print "ball" 

Defining a rendering function like

renderAll :: forall a. (Render a) => [a] -> IO ()
renderAll = mapM_ render

fails to do the job because the type [a] still constrains the input to a single instantiation of the type variable a. This can be fixed by using an existential type:

> data Renderable = forall a. (Render a) => Renderable a

In other words, we can inject any a implementing Render into a Renderable. So much for the constructor, but it would also be nice if Renderables could be rendered!

> instance Render Renderable where render (Renderable a) = render a

Finally we can write the generic renderAll function:

> :m Data.Foldable -- for sequenceA_ 
> let renderAll :: [Renderable] -> IO (); 
  renderAll = sequenceA_ . map render 
> renderAll [Renderable Player, Renderable Ball, Renderable Player]
"player"
"ball"
"player"

So now we can generically render any list of Renderables, and any Game should be able to provide us with a list of Renderables:

> let gameRenderables :: Game -> [Renderable]; 
  gameRenderables = -- ...

At this point we don’t care about how Games are constructed; we just care that they can provide lists of Renderable objects. So we also have a totally generic way to render a Game:

> instance Render Game where render = renderAll . gameRenderables

Game instances can then provide greater or fewer Renderables based on their states - they are not tied to their constructors. Furthermore, the Render instance for Game does not change if more Renderables are introduced later on.

Now there is a lot of juicy discussion over here regarding why not to use existentials in this way. In that example, one is using a typeclass Widget when a Widget is really an object, not a behavior! As for Renderable, the argument is to use data types

> data Renderable = Renderable { render :: IO () }

instead so we just capture the desired IO actions. Instead of instances we have functions:

> let toRenderable i = Renderable (print i)

This takes any Showable and turns it into a Renderable, for example. Supposing I have a composite object (like Game) full of Renderables, I can sequence their IO actions in the desired order and produce any desired additional IO to generate a new Renderable. For example:

> let listRend :: [Renderable] -> Renderable; 
  listRend = Renderable . mapM_ render

We generate a new Renderable by sequencing the renderings of the Renderables in the list. There is still a nice typeclass pattern here: toRenderable is a behavior that takes a type and returns its corresponding Renderable. In other words, we can defer the behaviors to an intermediate type which allows us to manipulate and sequence them in homogeneous containers.

> class Render a where toRenderable :: a -> Renderable
> instance Render Player where toRenderable p = Renderable $ print "player"
> instance Render Ball where toRenderable b = Renderable $ print "ball"
> render $ toRenderable Player
"player"
> render $ listRend [toRenderable Player, toRenderable Ball, toRenderable Player]
"player"
"ball"
"player"

This is all still completely statically type-checked. Yay! :-)