我认为这个问题涉及同一领域,但我看不出它如何适用于我的情况. 来自代理/邮箱处理器的通用回复?
这是背景.我有一些状态,现在我只想说它只包含一个玩家列表.可能会有更多,例如游戏等.我也有一个没有玩家的initialState.
type Player = {Name: string; Points: int}
type State = {Players: Player list}
let initialState = {Players = []}
Run Code Online (Sandbox Code Playgroud)
我有两种需要处理的"消息".查询,是将状态映射到某个值但不更改状态的函数.例如,返回显示最高得分的int.
和生成新状态的命令,但可以返回值.例如,将新玩家添加到集合中,并返回id或其他内容.
type Message<'T> =
| Query of (State -> 'T)
| Command of (State -> 'T * State)
Run Code Online (Sandbox Code Playgroud)
然后我们有一个可以响应消息的模型.但不幸的是,它使用了一个可变状态,我更喜欢使用MailboxProcessor和消息循环.
type Model(state: State) =
let mutable currentState = state
let HandleMessage (m: Message<'outp>) =
match m with
| Query q -> q currentState
| Command c ->
let n, s = c currentState
currentState <- s
n
member this.Query<'T> (q: State -> 'T) =
HandleMessage (Query q)
member this.Command<'T> (c: State -> 'T * State) =
HandleMessage (Command c)
// Query Methods
let HowMany (s: State) = List.length s.Players
let HasAny (s: State) = (HowMany s) > 0
let ShowAll (s: State) = s
// Command Methods
let AddPlayer (p: Player) (s: State) = (p, {s with Players = p::s.Players})
let model = new Model(initialState)
model.Command (AddPlayer {Name="Sandra"; Points=1000})
model.Query HasAny
model.Query HowMany
model.Query ShowAll
Run Code Online (Sandbox Code Playgroud)
显然,如果State论证本身是通用的,那就太好了.但是一步一步.
我试图用MailboxProcessor替换那个可变的currentState的所有东西都失败了.问题在于泛型和F#的静态性质,但我无法找到解决方法.
以下不起作用,但它显示了我想做的事情.
type Player = {Name: string; Points: int}
type State = {Players: Player list}
let initialState = {Players = []}
type Message<'T> =
| Query of (State -> 'T) * AsyncReplyChannel<'T>
| Command of (State -> 'T * State) * AsyncReplyChannel<'T>
type Model(state: State) =
let innerModel =
MailboxProcessor.Start(fun inbox ->
let rec messageLoop (state: State) =
async {
let! msg = inbox.Receive()
match (msg: Message<'outp>) with
| Query (q, replyChannel) ->
replyChannel.Reply(q state)
return! messageLoop state
| Command (c, replyChannel) ->
let result, newState = c state
replyChannel.Reply(result)
return! messageLoop(newState)
}
messageLoop initialState)
member this.Query<'T> (q: State -> 'T) =
innerModel.PostAndReply(fun chan -> Query(q , chan))
member this.Command<'T> (c: State -> 'T * State) =
innerModel.PostAndReply(fun chan -> Command(c, chan))
// Query Methods
let HowMany (s: State) = List.length s.Players
let HasAny (s: State) = (HowMany s) > 0
let ShowAll (s: State) = s
//// Command Methods
let AddPlayer (p: 'T) (s: State) = {s with Players = p::s.Players}
let model = new Model(initialState)
model.Command (AddPlayer {Name="Joe"; Points=1000})
model.Query HowMany
model.Query HasAny
model.Query ShowAll
Run Code Online (Sandbox Code Playgroud)
正如斯科特所提到的,问题在于你的Message<'T>类型是通用的,但它的使用方式仅限'T于代理体内的单一类型.
但是,代理并不需要对该值执行任何操作'T.它只是将函数(包含在消息中)的结果传递给异步回复通道(也包含在消息中).因此,我们可以通过'T从代理中完全隐藏类型的值并使消息成为仅包含函数的值来解决此问题:
type Message =
| Query of (State -> unit)
| Command of (State -> State)
Run Code Online (Sandbox Code Playgroud)
你甚至可以只使用一个函数State -> State(查询是一个总是返回相同状态的函数),但我想保留原始结构.
在代理内部,您现在可以只调用该函数,对于命令,切换到新状态:
type Model(state: State) =
let innerModel =
MailboxProcessor<Message>.Start(fun inbox ->
let rec messageLoop (state: State) =
async {
let! msg = inbox.Receive()
match msg with
| Query q ->
q state
return! messageLoop state
| Command c ->
let newState = c state
return! messageLoop(newState)
}
messageLoop initialState)
Run Code Online (Sandbox Code Playgroud)
有趣的是成员.它们将是通用的,仍然用于PostAndAsyncReply创建类型的值AsyncReplyChannel<'T>.但是,范围'T可以限制在函数体中,因为它们现在将构造Query或Command值自己将回复直接发布到我们刚刚创建的通道:
member this.Query<'T> (q: State -> 'T) =
innerModel.PostAndReply(fun chan -> Query(fun state ->
let res = q state
chan.Reply(res)))
member this.Command<'T> (c: State -> 'T * State) =
innerModel.PostAndReply(fun chan -> Command(fun state ->
let res, newState = c state
chan.Reply(res)
newState))
Run Code Online (Sandbox Code Playgroud)
实际上,这与您原来的解决方案非常相似.我们只需要将处理'T代理体的值的所有代码提取到泛型方法中.
编辑:添加一个在状态上也是通用的版本:
type Message<'TState> =
| Query of ('TState -> unit)
| Command of ('TState -> 'TState)
type Model<'TState>(initialState: 'TState) =
let innerModel =
MailboxProcessor<Message<'TState>>.Start(fun inbox ->
let rec messageLoop (state: 'TState) =
async {
let! msg = inbox.Receive()
match msg with
| Query q ->
q state
return! messageLoop state
| Command c ->
let newState = c state
return! messageLoop(newState)
}
messageLoop initialState)
member this.Query<'T> (q: 'TState -> 'T) =
innerModel.PostAndReply(fun chan -> Query(fun state ->
let res = q state
chan.Reply(res)))
member this.Command<'T> (c: 'TState -> 'T * 'TState) =
innerModel.PostAndReply(fun chan -> Command(fun state ->
let res, newState = c state
chan.Reply(res)
newState))
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
277 次 |
| 最近记录: |