1

我有一本操作字典:

type INumerics<'T> =
  abstract Zer : 'T
  abstract Add : 'T * 'T -> 'T
  abstract Sub : 'T * 'T -> 'T
  abstract Mul : 'T * 'T -> 'T
  abstract Div : 'T * 'T -> 'T
  abstract Neq : 'T * 'T -> bool

使用辅助功能:

let inline add (x : 'T) (y : 'T) : 'T   = (+)  x y
let inline sub (x : 'T) (y : 'T) : 'T   = (-)  x y
let inline mul (x : 'T) (y : 'T) : 'T   = (*)  x y
let inline div (x : 'T) (y : 'T) : 'T   = (/)  x y
let inline neq (x : 'T) (y : 'T) : bool = (<>) x y 

然后我们有一个使用 MailboxProcessor 代理的简单计算器:

type Agent<'T> = MailboxProcessor<'T>

type CalculatorMsg<'T> =
    | Add of 'T * 'T * AsyncReplyChannel<'T>
    | Sub of 'T * 'T * AsyncReplyChannel<'T> 
    | Mul of 'T * 'T * AsyncReplyChannel<'T>  
    | Div of 'T * 'T * AsyncReplyChannel<'T>

type CalculatorAgent< ^T when ^T : (static member get_Zero : unit -> ^T) 
                         and  ^T : (static member Zero : ^T) 
                         and  ^T : (static member (+)  : ^T * ^T -> ^T)
                         and  ^T : (static member (-)  : ^T * ^T -> ^T)
                         and  ^T : (static member (*)  : ^T * ^T -> ^T)
                         and  ^T : (static member (/)  : ^T * ^T -> ^T)
                         and  ^T : equality >() =
    let agent =
        let ops = 
            { new INumerics<'T> with 
                member ops.Zer       = LanguagePrimitives.GenericZero<'T> 
                member ops.Add(x, y) = (x, y) ||> add  
                member ops.Sub(x, y) = (x, y) ||> sub
                member ops.Mul(x, y) = (x, y) ||> mul   
                member ops.Div(x, y) = (x, y) ||> div   
                member ops.Neq(x, y) = (x, y) ||> neq }

        Agent<CalculatorMsg<'T>>.Start(fun inbox ->
            let rec loop () =
                async {
                    let! msg = inbox.TryReceive()
                    if msg.IsSome then
                        match msg.Value with 
                        | Add (x, y, rep) ->
                            printfn "Adding %A and %A ..." x y
                            let res = ops.Add(x, y)
                            res |> rep.Reply  
                            return! loop()
                        | Sub (x, y, rep) -> 
                            printfn "Subtracting %A from %A ..." y x
                            let res = ops.Sub(x, y) 
                            res |> rep.Reply  
                            return! loop()
                        | Mul (x, y, rep) ->
                            printfn "Multiplying %A by %A ... " y x
                            let res = ops.Mul(x, y)
                            res |> rep.Reply  
                            return! loop()
                        | Div (x, y, rep) ->
                            printfn "Dividing %A by %A ..." x y
                            if ops.Neq(y, ops.Zer) then 
                                let res = ops.Div(x, y)
                                res |> rep.Reply  
                            else
                                printfn "#DIV/0" 
                            return! loop()
                    else 
                        return! loop()
                }
            loop()
        )

    // timeout = infinit => t = -1
    let t = 1000

    member inline this.Add(x, y) =
        agent.PostAndTryAsyncReply((fun rep -> Add (x, y, rep)), t)
        |> Async.RunSynchronously
    member inline this.Subtract(x, y) =
        agent.PostAndTryAsyncReply((fun rep -> Sub (x, y, rep)), t)
        |> Async.RunSynchronously
    member inline this.Multiply(x, y) =
        agent.PostAndTryAsyncReply((fun rep -> Mul (x, y, rep)), t)
        |> Async.RunSynchronously
    member inline this.Divide(x, y) =
        agent.PostAndTryAsyncReply((fun rep -> Div (x, y, rep)), t)
        |> Async.RunSynchronously

作为一个使用示例,我们有:

let calculatorAgentI = new CalculatorAgent<int>()

(2, 1) |> calculatorAgentI.Add 
(2, 1) |> calculatorAgentI.Subtract
(2, 1) |> calculatorAgentI.Multiply
(2, 1) |> calculatorAgentI.Divide
(2, 0) |> calculatorAgentI.Divide

问题是 Add and Multiply 和 Last Divide 工作正常:

> 
Adding 2 and 1 ...
val it : int option = Some 3

> 
Multiplying 1 by 2 ... 
val it : int option = Some 2

> 
Dividing 2 by 0 ...
#DIV/0
val it : int option = None

一旦我们使用 Subtract 和 first Divide int option = None,我就会遇到麻烦,以下是我从任何操作中得到的唯一输出:

> 
val it : int option = None

只要我仔细考虑,我无法弄清楚“减”/“除”部分或“接收”/“回复”操作是否有问题。

4

2 回答 2

3

在附加调试器的情况下运行此代码,您将看到在尝试sub从代理内部运行该函数时收到 System.NotSupportedException:

System.NotSupportedException occurred
  HResult=0x80131515
  Message=Specified method is not supported.
  Source=FSI-ASSEMBLY
  StackTrace:
  at FSI_0002.ops@60.FSI_0002-INumerics`1-Sub(T X1, T X2) 

你得到的原因val it : int option = None是因为你已经指定了一个 1 秒的超时,并且你在抛出异常之后达到了这个超时。

直接调用sub函数可以正常工作,但通过CalculatorAgent类调用则不行。这是因为在这种情况下,类型参数是在类上定义的,并且在 F# 中对类的结构类型约束的使用存在限制。我建议阅读静态解析类型参数及其限制。

于 2018-01-18T21:36:15.193 回答
2

该行为似乎是异常或错误。我还期望这对所有操作都具有相同的行为。

在任何情况下,您都可以通过捕获ops静态内联成员(而不是在类型上使用静态解析的类型参数)来解决此问题。以下对我来说很好:

type CalculatorAgent<'T>(ops:INumerics<'T>) = 
  let agent = 
    Agent<CalculatorMsg<'T>>.Start(fun inbox ->
      let rec loop () = async {
        let! msg = inbox.TryReceive()
        match msg with 
        | Some(Add (x, y, rep)) ->
            printfn "Adding %A and %A ..." x y
            let res = ops.Add(x, y)
            res |> rep.Reply  
            return! loop()
        | Some(Sub (x, y, rep)) -> 
            printfn "Subtracting %A from %A ..." y x
            let res = ops.Sub(x, y) 
            res |> rep.Reply  
            return! loop()
        | Some(Mul (x, y, rep)) ->
            printfn "Multiplying %A by %A ... " y x
            let res = ops.Mul(x, y)
            res |> rep.Reply  
            return! loop()
        | Some(Div (x, y, rep)) ->
            printfn "Dividing %A by %A ..." x y
            if ops.Neq(y, ops.Zer) then 
                let res = ops.Div(x, y)
                res |> rep.Reply  
            else
                printfn "#DIV/0" 
            return! loop()
        | _ ->
            return! loop() }
      loop() )

  // timeout = infinit => t = -1
  let t = 1000

  member this.Add(x, y) =
    agent.PostAndTryAsyncReply((fun rep -> Add (x, y, rep)), t)
    |> Async.RunSynchronously
  member this.Subtract(x, y) =
    agent.PostAndTryAsyncReply((fun rep -> Sub (x, y, rep)), t)
    |> Async.RunSynchronously
  member this.Multiply(x, y) =
    agent.PostAndTryAsyncReply((fun rep -> Mul (x, y, rep)), t)
    |> Async.RunSynchronously
  member this.Divide(x, y) =
    agent.PostAndTryAsyncReply((fun rep -> Div (x, y, rep)), t)
    |> Async.RunSynchronously

type CalculatorAgent = 
  static member inline Create() = 
    let ops = 
      { new INumerics<_> with 
          member ops.Zer = LanguagePrimitives.GenericZero<_> 
          member ops.Add(x, y) = x + y
          member ops.Sub(x, y) = x - y
          member ops.Mul(x, y) = x * y
          member ops.Div(x, y) = x / y
          member ops.Neq(x, y) = x <> y }
    CalculatorAgent<_>(ops)

let calculatorAgentI = CalculatorAgent.Create<int>()

(2, 1) |> calculatorAgentI.Add 
(2, 1) |> calculatorAgentI.Subtract
(2, 1) |> calculatorAgentI.Multiply
(2, 1) |> calculatorAgentI.Divide
(2, 0) |> calculatorAgentI.Divide

也就是说,我认为您真正需要通用数字代码的情况非常罕见 - 所以我怀疑完全避免引入所有这些复杂性并只为特定数字类型编写代码可能会更好。

于 2018-01-18T22:55:39.890 回答