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