Strongly typed events in Haskell
Asked Answered
M

1

6

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?

Margalo answered 25/2, 2014 at 20:58 Comment(5)
Why not make deserialize a member of Event? The problem is that you're saying that deserialize can return any Event, but you're specifically saying that it returns a GameEvent. If it's part of the Event class, then you get the polymorphism you're looking for.Slavin
Also, it's not that Java's type system is more expressive, it's that you're trying to make Haskell's type system act like Java's, and that just won't work.Slavin
@Slavin Thanks a lot. I don't know why I didn't think to make deserialise a member of Event 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
PS if you want to put that in a full answer, I'll accept it and fifteen points will be yours :)Margalo
Worth taking a look at this statically typed Haskell implementation of ES: gist.github.com/Fristi/7327904 and the original keynote is slideshare.net/mobile/chris.e.richardson/…Violence
S
4

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
    ...
Slavin answered 25/2, 2014 at 21:39 Comment(2)
Won't you still have the core problem that the caller gets to decide which implementation of deserialise to call, rather than it being decided by the contents of the Bytestring?Arlina
@Arlina yes, but now that the typeclass requires an implementation of deserialize, you can be sure that whatever type the caller asks for will have an appropriate implementation of deserialize available.Gaucherie

© 2022 - 2024 — McMap. All rights reserved.