F# 使用MailboxProcessor的通用查询和命令
我认为这个问题涉及到同一个领域,但我看不出它如何适用于我的情况。 这是背景。我有一些状态,就目前而言,它只包含一个球员名单。可能还有更多,例如游戏等。我还有一个初始状态,没有玩家F# 使用MailboxProcessor的通用查询和命令,f#,F#,我认为这个问题涉及到同一个领域,但我看不出它如何适用于我的情况。 这是背景。我有一些状态,就目前而言,它只包含一个球员名单。可能还有更多,例如游戏等。我还有一个初始状态,没有玩家 type Player = {Name: string; Points: int} type State = {Players: Player list} let initialState = {Players = []} 我有两种“信息”需要处理。 查询,这些函数将状态映射到某个值,但不更改状态。例如,返回显示最
type Player = {Name: string; Points: int}
type State = {Players: Player list}
let initialState = {Players = []}
我有两种“信息”需要处理。
查询,这些函数将状态映射到某个值,但不更改状态。例如,返回显示最高分数的整数
以及生成新状态但可以返回值的命令。
例如,向集合中添加一个新玩家,并返回一个id或其他任何内容
type Message<'T> =
| Query of (State -> 'T)
| Command of (State -> 'T * State)
键入消息'T*状态)
然后我们有了一个模型,可以响应消息。但不幸的是,它使用了可变状态,我更喜欢使用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
类型模型(状态:状态)=
让可变currentState=状态
let HandleMessage(m:Message(q:State->'T)=
HandleMessage(查询q)
请记住此命令。命令0
让ShowAll(s:State)=s
//指挥方法
让AddPlayer(p:Player)(s:State)=(p,{s with Players=p::s.Players})
让模型=新模型(初始状态)
命令(AddPlayer{Name=“Sandra”;Points=1000})
model.Query HasAny
模型。查询有多少
model.queryshowall
显然,如果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
type Player={Name:string;Points:int}
类型State={Players:playerlist}
让initialState={Players=[]}
键入消息)和
|查询(q,回复频道)->
replyChannel.Reply(q状态)
回来!消息循环状态
|命令(c,replyChannel)->
let result,newState=c state
replyChannel.Reply(结果)
回来!messageLoop(新闻状态)
}
messageLoop(初始状态)
这个成员。查询(q,chan))
成员:指挥部(c,chan))
//查询方法
让多少(s:State)=列出长度为s的玩家
设HasAny(s:State)=(有多少个s)>0
让ShowAll(s:State)=s
////指挥方法
让AddPlayer(p:'T)(s:State)={s with Players=p::s.Players}
让模型=新模型(初始状态)
命令(AddPlayer{Name=“Joe”;Points=1000})
模型。查询有多少
model.Query HasAny
model.queryshowall
问题在于通用的消息正如Scott所提到的,问题在于您的消息状态(查询是一个始终返回相同状态的函数),但我希望保留原始结构
在代理内部,您现在只需调用函数,对于命令,切换到新状态:
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)
事实上,这与您最初的解决方案非常相似。我们只需将处理'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))
类型消息单元)
|命令('TState->'TState)
类型模型
q状态
回来!消息循环状态
|命令c->
让newState=c状态
回来!messageLoop(新闻状态)
}
messageLoop(初始状态)
此成员(查询'T')=
innerModel.PostAndReply(乐趣->查询(乐趣状态->
设res=q状态
陈议员的答覆(res)))
成员。命令“T*”T状态)=
PostAndReply(fun chan->Command(fun state->
设res,newState=c状态
陈议员的答覆(res)
新闻状态)
Tomas,这正是我想要做的。我可以看到代理正在被传递函数,所以它不需要做任何事情,只需要返回结果。这就是为什么我花了这么长时间在这个问题上,想到一个通用的方法可能是不可能的,真的很烦人。我认为把州本身变成一个通用的方法是一个太远的桥梁,对吗?e、 差不多。类型Model@RichardDalton使国家通用应该完全可行。您只需将消息
和模型
类型设置为泛型,并将初始状态
作为构造函数参数传递给模型
,但这样它就可以正常工作了!这就是我正在尝试的,但我再次遇到警告和错误。我会坚持下去的忘了我刚才说的吧。它起作用了。亲爱的,那一定是个愚蠢的问题。。。这应该很容易!我在我的答案中添加了一个通用版本。我不会排除这个答案。如果我正在为一项特定的任务构建一个特定的代理,这肯定是我会采取的方法。而且,我还没有完全售出,普通的要比普通的好。但我真的很想看看如何构建一个域模型。我还认为,在某些情况下,将操作作为域模型的一部分将非常有意义。如果有一个封闭的操作集,那么这肯定是一个很好的方法。
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)
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))
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))