gpt4 book ai didi

f# - 使用 MailboxProcessor 的通用查询和命令

转载 作者:行者123 更新时间:2023-12-04 06:57:52 26 4
gpt4 key购买 nike

我认为这个问题涉及同一领域,但我看不出它如何适用于我的情况。
Generic reply from agent/mailboxprocessor?

这是背景。我有一些状态,现在只能说它只包含一个玩家列表。可能还有更多,例如游戏等。我也有一个没有玩家的初始状态。

type Player = {Name: string; Points: int}
type State = {Players: Player list}
let initialState = {Players = []}

我有两种“消息”需要处理。
查询是将状态映射到某个值但不更改状态的函数。例如。返回一个 int 显示最高分。

以及产生新状态但可以返回值的命令。
例如,向集合中添加一个新玩家,并返回一个 id 或其他任何内容。
type Message<'T> =
| Query of (State -> 'T)
| Command of (State -> 'T * State)

然后我们有一个可以响应消息的模型。但不幸的是使用可变状态,我更喜欢使用 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

显然,如果 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

最佳答案

正如斯科特提到的,问题在于您的 Message<'T> type 是通用的,但它的使用方式限制了 'T到代理主体内的单一类型。

但是,代理实际上不需要对值 'T 做任何事情。 .它只是将函数的结果(包含在消息中)传递到异步回复 channel (也包含在消息中)。因此,我们可以通过完全隐藏类型 'T 的值来解决这个问题。来自代理并使消息成为仅带有函数的值:

type Message =
| Query of (State -> unit)
| Command of (State -> State)

你甚至可以只使用一个函数 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)

有趣的是成员。它们将是通用的并且仍然使用 PostAndAsyncReply创建类型为 AsyncReplyChannel<'T> 的值.但是, 'T的范围可以限制在函数体中,因为它们现在将构造 QueryCommand自己直接将回复发布到我们刚刚创建的 channel 的值:
  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))

实际上,这与您的原始解决方案非常相似。我们只需要提取处理 '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))

关于f# - 使用 MailboxProcessor 的通用查询和命令,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34010062/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com