【问题标题】:Issue with a generic dictionary of operations通用操作字典的问题
【发布时间】:2018-01-18 16:38:34
【问题描述】:

我有一本操作字典:

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

一旦我们使用返回 int option = None 的减法和第一次除法,我就会遇到麻烦,以下是我从任何操作中得到的唯一输出:

> 
val it : int option = None

想了很久,还是搞不清楚是“减”/“除”部分还是“接收”/“回复”操作有问题。

【问题讨论】:

  • 问题不在于超时或异步回复。即使我将超时设置为无限或使用同步回复,问题仍然存在。
  • 如果我删除类型参数'T 并将代码设置为特定类型,如floatint,它将正常工作。
  • 减法和除法似乎存在特定问题......与“sub”和“div”辅助函数有关!!!
  • 如果我将“sub”函数的定义更改为let inline sub x y = x,那么它可以工作。无论出于何种原因,我都无法理解,subdiv 辅助函数定义存在问题。

标签: dictionary generics f# operations numerics


【解决方案1】:

在附加调试器的情况下运行此代码,您将看到在尝试从代理内部运行 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# 中对类的结构类型约束的使用存在限制。我建议阅读Statically Resolved Type Parameters 及其局限性。

【讨论】:

  • 非常感谢。两点:1)在类定义中更改为统计解析的参数,即type CalculatorAgent&lt; ^T when ^T : ...&gt;,并为成员方法添加类型注释和约束并没有解决问题。 2)这种情况只发生在减法和除法上,而不是加法和乘法,这不是矛盾吗?!
  • 对不起,我应该澄清一下,你不能在类型上使用静态解析的类型参数。看看我答案中链接中的图表。类型参数需要限制为内联函数,在编译时可以正确解析。
  • 我知道 SRP 的局限性,但是,我想指出我在具有成员约束的类类型上尝试过,但令我惊讶的是,我没有收到任何错误,这很奇怪对我来说也是如此。
【解决方案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

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

【讨论】:

  • 另一方面,当我在type CalculatorAgent&lt; ^T when ...&gt; 中使用了统计解析的参数 (^T) 时,我应该不会收到错误或其他信息。我期望如此,但我什么也没得到。我在其他几个课程上尝试过,奇怪的是,没有问题。
猜你喜欢
  • 2022-06-10
  • 1970-01-01
  • 2013-11-26
  • 2012-10-02
  • 1970-01-01
  • 2021-08-17
  • 2018-08-11
  • 2021-07-22
相关资源
最近更新 更多