定义具有多种消息类型的消息传递域
Defining a Message Passing domain with very many message types
到目前为止,我见过的大多数 F# 消息传递示例都使用 2-4 种消息类型,并且能够利用模式匹配将每条消息定向到其正确的处理程序函数。
对于我的应用程序,由于处理方式和所需参数的不同性质,我需要数百种独特的消息类型。到目前为止,每种消息类型都是它自己的记录类型,并附加了一个标记接口,因为在一个可区分的联合中包含数百种类型不会很漂亮 - 这些类型的模式匹配也不会很漂亮。因此,我目前正在使用反射来查找消息的正确处理函数。
有没有更好、更实用的方法?也许更聪明的方式来定义这样的域?我想在编译时尽可能多地执行正确性,但目前我正在查找基于自定义属性的处理程序函数,并在 运行 时间检查它们的签名。
据我所知,我无法使用 .NET 自定义属性强制执行函数的签名,并且由于有太多类型无法进行实际模式匹配,我不能(据我所知)使用单个通用消息处理函数。我尝试使用通用包装函数作为所有处理程序的 "interface",并且只将自定义属性附加到这个处理程序,但这并没有授予包装函数属性并使它们通过基于该属性的反射可见(我是 .NET 的新手)。
我考虑过将处理函数作为成员附加到它们各自的记录类型的可能性,这将避免反射的需要并在编译时强制执行一些额外的正确性。但是,将所有这些功能都呈现在客户端并没有多大意义。
这个问题有点宽泛,我会试一试。
让我们从类型开始。我们的消息将具有类型和内容。您可以添加更多字段,例如 messageId
、sender
、receiver
等
type MessageType = MessageType of string
type Message<'T> = {
messageType : MessageType
message : 'T
}
同样,我们的处理程序类型会将类型和处理程序函数配对。
type HandlerResult = Result<string, string>
type MessageHandler<'T> = {
messageType : MessageType
handlerF : Message<'T> -> HandlerResult
}
我们希望有一个地方可以用它们的类型注册所有处理程序。字典是理想的,因为它很快:
let Handlers = System.Collections.Generic.Dictionary<MessageType, MessageHandler<obj>>()
唯一的问题是字典不能有通用类型,所以这里的所有处理程序都将是 MessageHandler<obj>
类型。因此,我们需要能够将 <'T>
消息和处理程序转换为 <obj>
消息和处理程序并返回。 Se这里我们有一个辅助函数:
let ofMessageGen (msg: Message<obj>) : Message<_> = {
messageType = msg.messageType
message = unbox msg.message
}
和一个将处理程序函数注册为 <obj>
处理程序的函数:
let registerHandler (handlerF:Message<'T> -> HandlerResult) =
let handler = {
messageType = MessageType <| (typeof<'T>).FullName
handlerF = ofMessageGen >> handlerF
}
Handlers.Add(handler.messageType, handler )
这样我们就可以为任何类型注册处理程序:
registerHandler (fun msg -> sprintf "String message: %s" msg.message |> Ok )
registerHandler (fun msg -> sprintf "int message: %d" msg.message |> Ok )
registerHandler (fun msg -> sprintf "float message: %f" msg.message |> Ok )
这是我们的通用消息处理程序:
let genericHandler (msg:Message<obj>) : HandlerResult =
match Handlers.TryGetValue msg.messageType with
| false, _ -> Error <| sprintf "No Handler for message: %A" msg
| true , handler -> handler.handlerF msg
创建消息:
let createMessage (m:'T) = {
messageType = MessageType <| (typeof<'T>).FullName
message = box m
}
然后像这样测试它:
createMessage "Hello" |> genericHandler |> printfn "%A"
createMessage 123 |> genericHandler |> printfn "%A"
createMessage 123.4 |> genericHandler |> printfn "%A"
createMessage true |> genericHandler |> printfn "%A"
// Ok "String message: Hello"
// Ok "int message: 123"
// Ok "float message: 123.400000"
// Error
// "No Handler for message: {messageType = MessageType "System.Boolean";
// message = true;}"
到目前为止,我见过的大多数 F# 消息传递示例都使用 2-4 种消息类型,并且能够利用模式匹配将每条消息定向到其正确的处理程序函数。
对于我的应用程序,由于处理方式和所需参数的不同性质,我需要数百种独特的消息类型。到目前为止,每种消息类型都是它自己的记录类型,并附加了一个标记接口,因为在一个可区分的联合中包含数百种类型不会很漂亮 - 这些类型的模式匹配也不会很漂亮。因此,我目前正在使用反射来查找消息的正确处理函数。
有没有更好、更实用的方法?也许更聪明的方式来定义这样的域?我想在编译时尽可能多地执行正确性,但目前我正在查找基于自定义属性的处理程序函数,并在 运行 时间检查它们的签名。
据我所知,我无法使用 .NET 自定义属性强制执行函数的签名,并且由于有太多类型无法进行实际模式匹配,我不能(据我所知)使用单个通用消息处理函数。我尝试使用通用包装函数作为所有处理程序的 "interface",并且只将自定义属性附加到这个处理程序,但这并没有授予包装函数属性并使它们通过基于该属性的反射可见(我是 .NET 的新手)。
我考虑过将处理函数作为成员附加到它们各自的记录类型的可能性,这将避免反射的需要并在编译时强制执行一些额外的正确性。但是,将所有这些功能都呈现在客户端并没有多大意义。
这个问题有点宽泛,我会试一试。
让我们从类型开始。我们的消息将具有类型和内容。您可以添加更多字段,例如 messageId
、sender
、receiver
等
type MessageType = MessageType of string
type Message<'T> = {
messageType : MessageType
message : 'T
}
同样,我们的处理程序类型会将类型和处理程序函数配对。
type HandlerResult = Result<string, string>
type MessageHandler<'T> = {
messageType : MessageType
handlerF : Message<'T> -> HandlerResult
}
我们希望有一个地方可以用它们的类型注册所有处理程序。字典是理想的,因为它很快:
let Handlers = System.Collections.Generic.Dictionary<MessageType, MessageHandler<obj>>()
唯一的问题是字典不能有通用类型,所以这里的所有处理程序都将是 MessageHandler<obj>
类型。因此,我们需要能够将 <'T>
消息和处理程序转换为 <obj>
消息和处理程序并返回。 Se这里我们有一个辅助函数:
let ofMessageGen (msg: Message<obj>) : Message<_> = {
messageType = msg.messageType
message = unbox msg.message
}
和一个将处理程序函数注册为 <obj>
处理程序的函数:
let registerHandler (handlerF:Message<'T> -> HandlerResult) =
let handler = {
messageType = MessageType <| (typeof<'T>).FullName
handlerF = ofMessageGen >> handlerF
}
Handlers.Add(handler.messageType, handler )
这样我们就可以为任何类型注册处理程序:
registerHandler (fun msg -> sprintf "String message: %s" msg.message |> Ok )
registerHandler (fun msg -> sprintf "int message: %d" msg.message |> Ok )
registerHandler (fun msg -> sprintf "float message: %f" msg.message |> Ok )
这是我们的通用消息处理程序:
let genericHandler (msg:Message<obj>) : HandlerResult =
match Handlers.TryGetValue msg.messageType with
| false, _ -> Error <| sprintf "No Handler for message: %A" msg
| true , handler -> handler.handlerF msg
创建消息:
let createMessage (m:'T) = {
messageType = MessageType <| (typeof<'T>).FullName
message = box m
}
然后像这样测试它:
createMessage "Hello" |> genericHandler |> printfn "%A"
createMessage 123 |> genericHandler |> printfn "%A"
createMessage 123.4 |> genericHandler |> printfn "%A"
createMessage true |> genericHandler |> printfn "%A"
// Ok "String message: Hello"
// Ok "int message: 123"
// Ok "float message: 123.400000"
// Error
// "No Handler for message: {messageType = MessageType "System.Boolean";
// message = true;}"