Ben*_*son 4 c# polymorphism haskell constants
我对这个问题有一个后续问题.什么是惯用的Haskell相当于面向对象语言中的多态类级常量?
我正在尝试使用Event Store和Haskell 进行事件采购.我一直试图找出保存和加载事件的逻辑.
Event Store基于事件流的概念; 在面向对象的域模型中,事件流和聚合之间通常存在1:1的关系.您可以将流组织成类别 ; 通常,您的域模型中每个聚合类都有一个类别.这是一个如何在C#中对其进行建模的草图:
interface IEventStream<T> where T : Event
{
string Category { get; }
string StreamName { get; }
IEnumerable<T> Events { get; }
}
class PlayerEventStream : IEventStream<PlayerEvent>
{
public string Category { get { return "Player"; } }
public string StreamName { get; private set; }
public IEnumerable<PlayerEvent> Events { get; private set; }
public PlayerEventStream(int aggregateId)
{
StreamName = Category + "-" + aggregateId;
}
}
class GameEventStream : IEventStream<GameEvent>
{
public string Category { get { return "Game"; } }
public string StreamName { get; private set; }
public IEnumerable<GameEvent> Events { get; private set; }
public GameEventStream(int aggregateId)
{
StreamName = Category + "-" + aggregateId;
}
}
class EventStreamSaver
{
public void Save(IEventStream stream)
{
CreateStream(stream.StreamName);
AddToCategory(stream.StreamName, stream.Category);
SaveEvents(stream.StreamName, stream.Events);
}
}
Run Code Online (Sandbox Code Playgroud)
此代码确保GameEvent永远不会发送到播放器的事件流,反之亦然,并且所有类别都已正确分配.我正在使用多态常量Category来帮助保护这个不变量,并使以后更容易添加新的流类型.
这是我第一次尝试将此结构转换为Haskell:
data EventStream e = EventStream AggregateID [e]
streamName :: Event e => EventStream e -> String
streamName (EventStream aggregateID (e:events)) = (eventCategory e) ++ '-':(toString aggregateID)
class Event e where
eventCategory :: e -> String
-- and some other functions related to serialisation
instance Event PlayerEvent where
eventCategory _ = "Player"
instance Event GameEvent where
eventCategory _ = "Game"
saveEventStream :: Event e => EventStream e -> IO ()
saveEventStream stream@(EventStream id events) =
let name = streamName stream
category = eventCategory $ head events
in do
createStream name
addToCategory name category
saveEvents name events
Run Code Online (Sandbox Code Playgroud)
这非常难看.类型系统需要在其签名中eventCategory提及e某个地方,即使它没有在函数中的任何地方使用.如果流不包含任何事件(因为我试图将类别附加到事件类型),它也会失败.
我知道我正在尝试在Haskell中编写C# - 有没有更好的方法来实现这种类型的多态常量?
更新:根据要求,这里是我认为do块中当前未实现的存根应具有的类型签名:
type StreamName = String
type CategoryName = String
createStream :: StreamName -> IO ()
addToCategory :: StreamName -> CategoryName -> IO ()
saveEvents :: Event e => StreamName -> [e] -> IO ()
Run Code Online (Sandbox Code Playgroud)
这些函数负责与数据库通信 - 设置模式并序列化事件.
有些人建议存在类型,但除非我非常误解,否则你想要将某些偶数流限制为某些类型.
首先,
data EventStream e = EventStream AggregateID [e]
streamName :: Event e => EventStream e -> String
streamName (EventStream aggregateID (e:events)) = (eventCategory e) ++ '-':(toString aggregateID)
Run Code Online (Sandbox Code Playgroud)
应该好像很奇怪.你调用eventCategory第一个偶数并丢弃其余的,所以你假设所有事件的类别是相同的.但是,当然,eventCategory可以为事件的不同值返回不同的字符串.如果没有事件,你必须这样做eventCategory undefined.
一个想法是改变以下类型eventCategory:
data Proxy p = Proxy
class Event e where
eventCategory :: Proxy e -> String
Run Code Online (Sandbox Code Playgroud)
现在,函数不可能为事件的不同值返回不同的字符串,因为它无法访问实际值.换句话说,eventCategory仅取决于类型,而不取决于价值.
另一种可能性是遵循c#代码,即类别是流的属性,而不是事件:
{-# LANGUAGE MultiParamTypeClasses #-}
import Data.ByteString
class Event e where
deserialize :: ByteString -> e
... other stuff
class Event e => EventStream t e where
category :: t e -> String
aggregateId :: t e -> Int
events :: t e -> [e]
name :: t e -> String
name s = category s ++ "-" ++ show (aggregateId s)
Run Code Online (Sandbox Code Playgroud)
该EventStream类型类与接口紧密对应.
请注意name类型类中的内容如何,但您可以在不知道正在使用哪个实例的情况下编写它.您可以轻松地将其移出类型类,但实现可能会决定它将定义一个自定义名称,该名称将覆盖默认定义.
然后你定义你的事件:
data PlayerEvent = ...
instance Event PlayerEvent where ...
data GameEvent = ...
instance Event GameEvent where ...
Run Code Online (Sandbox Code Playgroud)
现在流类型:
data PlayerEventStream e = PES Int [e]
instance EventStream PlayerEventStream PlayerEvent where
category = const "Player"
aggregateId (PES n _) = n
events (PES _ e) = e
data GameEventStream e = GES Int [e]
instance EventStream GameEventStream GameEvent where
category = const "Game"
aggregateId (GES n _) = n
events (GES _ e) = e
Run Code Online (Sandbox Code Playgroud)
请注意,事件类型是同构的,但仍然是不同的类型.你不能拥有PlayerEvents GameEventStream(或者更确切地说,你可以,但是没有包含s的EventStream实例).你甚至可以加强这种关系:GameEventStreamPlayerEvent
class Event e => EventStream t e | t -> e where
Run Code Online (Sandbox Code Playgroud)
这表示对于给定的流类型,可能只存在一种事件类型,因此定义两个这样的实例是类型错误:
instance EventStream PlayerEventStream PlayerEvent where
instance EventStream PlayerEventStream GameEvent where
Run Code Online (Sandbox Code Playgroud)
保存功能很简单:
saveEventStream :: EventStream t e => t e -> IO ()
saveEventStream s = do
createStream (name s)
addToCategory (name s) (category s)
saveEvents (name s) (events s)
Run Code Online (Sandbox Code Playgroud)
我不知道这是否是你真正想要的东西,但它似乎完成了与c#代码相同的事情.