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
anddeserialise
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?
deserialize
a member ofEvent
? The problem is that you're saying thatdeserialize
can return anyEvent
, but you're specifically saying that it returns aGameEvent
. If it's part of theEvent
class, then you get the polymorphism you're looking for. – Slavindeserialise
a member ofEvent
to start with. And, I agree that trying to write Java in Haskell is a mistake. It's still difficult to rewire one's brain so fundamentally though! – Margalo