Haskell中的强类型事件

6

我正在开发我的第一个“真正的”Haskell项目,同时尝试理解event sourcing。(它似乎很匹配;事件溯源是一种相当函数式的数据处理方式。)

我遇到了一个难题,即如何将我的事件反序列化为强类型的Haskell数据。这里有两股相互对立的力量:

  1. 不应该将事件应用于错误类型的聚合根。这个要求表明我需要为系统中的每个聚合根创建一个单独的事件类型:

    data PlayerEvent = PlayerCreated Name | NameUpdated Name

    data GameEvent = GameStarted PlayerID PlayerID | MoveMade PlayerID Move

    要使用这些事件,您可以使用类似于 applyEvent :: Game -> GameEvent -> Game 的函数。

  2. 我需要能够在强类型事件和JSON对象之间进行序列化和反序列化。这个要求表明我需要多态的 serialisedeserialise 函数:

    class Event e where serialise :: e -> ByteString

    deserialise :: Event e => ByteString -> e

那个最后的“deserialise”函数是问题所在。类型签名表示调用者可以请求“任何”“Event”实例,但是你得到的类型当然取决于进来的“ByteString”,并且在运行时确定。以下是一个无法编译的存根实现:
deserialise :: Event e => ByteString -> e
deserialise _ = GameStarted 0 0

并且错误信息:

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

在具有反射功能的面向对象语言中,这种事情很简单。我很难相信我找到了一个Java类型系统比Haskell更具表现力的问题。
我觉得我可能缺少一个关键的抽象。如何正确实现上述要求?

1
为什么不将deserialize作为Event的成员函数呢?问题在于你说deserialize可以返回任何Event,但是你又明确指出它返回的是一个GameEvent。如果它是Event类的一部分,那么你就可以得到所需的多态性。 - bheklilr
1
此外,Java 的类型系统并不比 Haskell 更具表现力,而是你试图让 Haskell 的类型系统像 Java 一样运作,这是行不通的。 - bheklilr
@bheklilr 非常感谢你。我不知道为什么一开始没有想到把'解串'(deserialise)作为'事件'(Event)的成员。并且,我同意尝试在Haskell中编写Java是一个错误。虽然这种思维方式的改变仍然很困难! - Benjamin Hodgson
如果你想把它放在一个完整的回答中,我会接受并给你十五个积分:) - Benjamin Hodgson
值得一看的是这个静态类型的Haskell实现ES:https://gist.github.com/Fristi/7327904,原始主题演讲在https://www.slideshare.net/mobile/chris.e.richardson/developing-functional-domain-models-with-event-sourcing-sbtb-sbtb2015 - Erik Kaplun
1个回答

4
如果您将deserialize作为Event类的成员函数,则不会出现任何问题:
class Event e where
    serialize :: e -> ByteString
    deserialize :: ByteString -> e

instance Event PlayerEvent where
    ...

instance Event GameEvent where
    ...

1
你不觉得核心问题仍然存在吗?调用者决定调用哪个反序列化实现,而不是由字节串的内容决定。 - Ben
@Ben 是的,但现在类型类要求实现deserialize,你可以确信调用者请求的任何类型都将有适当的deserialize实现可用。 - John

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接