From 49fcae9b281d0124013ab9b6b8b50eeb96875a88 Mon Sep 17 00:00:00 2001 From: Kevin Schneider Date: Thu, 4 Feb 2021 10:46:47 +0100 Subject: [PATCH] Enable callback evaluation for boxed IConvertible sequences aswell --- dev/Program.fs | 4 ++-- src/Dash.NET/Callback.fs | 31 +++++++++++++++++++++++++++++-- src/Dash.NET/DynamicInvoke.fs | 17 +++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/dev/Program.fs b/dev/Program.fs index b810972..1dcc54e 100644 --- a/dev/Program.fs +++ b/dev/Program.fs @@ -74,8 +74,8 @@ let callbackArrayInput = ], (fun (inputs:float []) (nclicks:float) -> [ - "output-1" @. Children => (Array.last inputs) * nclicks * 1. - "output-2" @. Children => (Array.last inputs) * nclicks * 2. + (Array.last inputs) * nclicks * 1. + (Array.last inputs) * nclicks * 2. ] ), State = [ diff --git a/src/Dash.NET/Callback.fs b/src/Dash.NET/Callback.fs index d3d32d2..2b49344 100644 --- a/src/Dash.NET/Callback.fs +++ b/src/Dash.NET/Callback.fs @@ -367,9 +367,12 @@ type Callback<'Function> match r with | :? seq as bindings -> + // The result is a sequence of boxed CallbackResultBindings + CallbackResponse.multiOut(bindings) | :? seq as boxedResults -> + // The result of the multioutput is a sequence of boxed types let outputs = handler?outputDependencies @@ -381,13 +384,37 @@ type Callback<'Function> outputs |> Seq.zip boxedResults |> Seq.map (fun (boxedRes, output) -> output.Id, output.Property, boxedRes) - ) else failwithf "amount of multi callback outputs did not match to the actual callback binding (expected %i but got %i)" (Seq.length outputs) (Seq.length boxedResults) - | _ -> failwithf "multi callback result %O was not a collection." r + | r when (isIConvertibleSeq (r.GetType())) -> + // The result of the multioutput is a boxed sequence of IConvertibles, e.g. + // [2;3;4], [|"1";"2"|]; seq{2.;3.} + + let primitiveSeq = + r + |> unbox + |> Seq.cast + + let outputs = + handler?outputDependencies + |> unbox> + + if (Seq.length outputs) = (Seq.length primitiveSeq) then + + CallbackResponse.multiOut( + outputs + |> Seq.zip primitiveSeq + |> Seq.map (fun (result, output) -> output.Id, output.Property, (box result)) + + ) + + else + failwithf "The amount of multi callback outputs returned by the callback function did not match to the amount of outputs defined by the callback dependency (expected %i vs %i). Make sure that the callback function returns a collection of results of length %i" (Seq.length primitiveSeq) (Seq.length outputs) (Seq.length outputs) + + | _ -> failwithf "multi callback result %O of type %O was not a supported collection (supported: seq, seq, seq). You might be able to circumvent this problem by boxing the return values of your results to generate a seq." r (r.GetType()) else diff --git a/src/Dash.NET/DynamicInvoke.fs b/src/Dash.NET/DynamicInvoke.fs index 8015e18..2377d24 100644 --- a/src/Dash.NET/DynamicInvoke.fs +++ b/src/Dash.NET/DynamicInvoke.fs @@ -31,6 +31,23 @@ module DynamicInvoke = f loop t + let isIConvertible (t:Type) = + t.GetInterfaces() + |> Array.exists (fun t -> t = typeof) + + let isSeq (t:Type) = + t.GetInterfaces() + |> Array.exists (fun t -> t = typeof) + + let isIConvertibleSeq (t:Type) = + if isSeq t then + let genericArgs = t.GetGenericArguments() + if genericArgs.Length > 0 then + isIConvertible genericArgs.[0] + else + false + else + false //This function is (as far as i see it) a 'necessary evil' for solving the problem of callbacks having arbitrary amounts of parameters. //However, just like DynamicObj in Plotly.NET, it is definately usable when correctly encapsulated to prevent direct usage.