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))