简体   繁体   English

通用操作字典出现问题

[英]Issue with a generic dictionary of operations

I have a dictionary of operations: 我有一个操作字典:

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

With helper functions: 具有辅助功能:

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 

Then we have a simple calculator using a MailboxProcessor agent: 然后我们有一个使用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

As a use example, we have: 作为一个使用示例,我们有:

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

The issue is that Add and Multiply and the Last Divide work ok: 问题是加法和乘法以及最后的分度工作正常:

> 
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

As soon as we use Subtract and first Divide which would return int option = None , I get into trouble and the following is the only output I would get from any of the operations: 一旦我们使用Subtract和first Divide会返回int option = None ,我就会遇到麻烦,以下是我从任何操作中获得的唯一输出:

> 
val it : int option = None

As long and hard as I think about it, I cannot figure out if there is a problem in the "subtract"/"divide" part or the "receive"/"reply" operations. 就我所考虑的时间和辛苦,我无法弄清楚“减” /“除”部分或“接收” /“答复”操作是否存在问题。

Running this code with the Debugger attached, you'll see that you get a System.NotSupportedException when trying to run the sub function from inside the agent: 在附加了调试器的情况下运行此代码,当您尝试从代理内部运行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) 

The reason you get val it : int option = None is because you have specified a 1-second timeout, and you are hitting that after the exception is thrown. 设置val it : int option = None的原因val it : int option = None是因为您指定了1秒的超时,并且在引发异常后将其击中。

Calling the sub function directly works fine, but calling it through the CalculatorAgent class does not. 直接调用sub可以正常工作,但不能通过CalculatorAgent类调用它。 This is because the type parameters are defined on the class in this case, and there are limitations on the use of structural type constraints on classes in F#. 这是因为在这种情况下,类型参数是在类上定义的,并且在F#中对类使用结构类型约束存在限制。 I would suggest reading up on Statically Resolved Type Parameters and their limitations. 我建议阅读静态解析类型参数及其限制。

The behaviour seems to be an irregularity or a bug. 该行为似乎是违规行为或错误。 I was also expecting that this would behave the same for all operations. 我还期望所有操作的行为相同。

In any case, you can workaround this by capturing ops in a static inline member (rather than using statically resolved type parameters on a type). 无论如何,您都可以通过捕获静态内联成员中的ops来解决此问题(而不是在类型上使用静态解析的类型参数)。 The following works fine for me: 以下对我有用:

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

That said, I think the cases where you really need generic numerical code are pretty rare - so I suspect it might be better to avoid introducing all this complexity altogether and just write the code for a specific numerical type. 就是说,我认为真正需要通用数字代码的情况很少见-所以我怀疑最好完全避免引入所有这些复杂性,而只为特定数字类型编写代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM