通用操作字典的问题
Issue with a generic 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
有辅助函数:
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
问题是加法和乘法以及最后除法工作正常:
>
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 和第一个 Divide return int option = None
,我就遇到了麻烦,以下是我从任何操作中得到的唯一输出:
>
val it : int option = None
想了很久也想不出是"subtract"/"divide"部分有问题还是"receive"/[=34=有问题] 操作。
运行 此代码附加了调试器,您会看到在尝试从代理内部 运行 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
class调用就不行了。这是因为在这种情况下,类型参数是在 class 上定义的,并且在 F# 中对 classes 使用结构类型约束存在限制。我建议阅读 Statically Resolved Type Parameters 及其局限性。
该行为似乎是异常或错误。我还期望所有操作的行为都相同。
在任何情况下,您都可以通过在静态内联成员中捕获 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 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
问题是加法和乘法以及最后除法工作正常:
>
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 和第一个 Divide return int option = None
,我就遇到了麻烦,以下是我从任何操作中得到的唯一输出:
>
val it : int option = None
想了很久也想不出是"subtract"/"divide"部分有问题还是"receive"/[=34=有问题] 操作。
运行 此代码附加了调试器,您会看到在尝试从代理内部 运行 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
class调用就不行了。这是因为在这种情况下,类型参数是在 class 上定义的,并且在 F# 中对 classes 使用结构类型约束存在限制。我建议阅读 Statically Resolved Type Parameters 及其局限性。
该行为似乎是异常或错误。我还期望所有操作的行为都相同。
在任何情况下,您都可以通过在静态内联成员中捕获 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
也就是说,我认为真正需要通用数字代码的情况非常少见 - 所以我怀疑最好避免完全引入所有这些复杂性,而只为特定的数字类型编写代码。