Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strongly typed events in Haskell

I'm working on my first 'real' Haskell project, and simultaneously trying to get my head around event sourcing. (It seemed like a good match; event sourcing is a rather functional way of looking at data.)

I've hit a wall trying to figure out how to deserialise my events into strongly typed Haskell data. There are two opposing forces at work here:

  1. It shouldn't be possible to apply an event to the wrong type of aggregate. This requirement suggests that I need a separate type of event for each aggregate in my system:

    data PlayerEvent = PlayerCreated Name | NameUpdated Name

    data GameEvent = GameStarted PlayerID PlayerID | MoveMade PlayerID Move

    To use these events you'd use functions with types like applyEvent :: Game -> GameEvent -> Game.

  2. I need to be able to serialise and deserialise between strongly-typed events and JSON objects. This requirement suggests I need polymorphic serialise and deserialise functions:

    class Event e where serialise :: e -> ByteString

    deserialise :: Event e => ByteString -> e

That last deserialise function is the problem. The type signature says that callers can ask for any instance of Event, but of course the type that you get back depends on the ByteString that came in and is determined at run-time.

Here's a stub implementation that won't compile:

deserialise :: Event e => ByteString -> e
deserialise _ = GameStarted 0 0

And the error message:

Could not deduce (e ~ GameEvent)
from the context (Event e)
  bound by the type signature for
             deserialise :: Event e => ByteString -> e
  at ...:20:16-41
  `e' is a rigid type variable bound by
      the type signature for deserialise :: Event e => ByteString -> e
      at ...:20:16
In the return type of a call of `GameStarted'
In the expression: GameStarted 0 0
In an equation for `deserialise':
    deserialise _ = GameStarted 0 0

This sort of thing is straightforward in an object-oriented language with reflection. I find it hard to believe that I've found a problem for which Java's type system is more expressive than Haskell's.

I feel like I must be missing a key abstraction here. What's the correct way to implement the above requirements?

like image 232
Benjamin Hodgson Avatar asked Feb 25 '14 20:02

Benjamin Hodgson


1 Answers

If you make deserialize a member of the Event class, then you won't have any problems:

class Event e where
    serialize :: e -> ByteString
    deserialize :: ByteString -> e

instance Event PlayerEvent where
    ...

instance Event GameEvent where
    ...
like image 74
bheklilr Avatar answered Oct 24 '22 05:10

bheklilr