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:
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
.
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?
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
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With