There I was, minding my own business, of writing generic higher order functions in asynchronous code, when she hit me, the task-state-machine. She has hit me before, but this time I think she left some traces of her, so I could saw a glimpse of that monster. So I'll report it here, even this will probably be closed "as designed", it might help some other guy struggling in seduction by her.
I haven't been able to repro it in short code, yet, but this is what is going on.
Observe the following code:
open System.Linq
type Shape = {x: int; y: int}
let returnFilter condition =
task {
if condition = "a" then
let filter1 : IQueryable<Shape> -> IQueryable<Shape> =
fun data -> data.Where(fun a -> a.x = 1)
return Some filter1
elif condition = "b" then
let filter2 : IQueryable<Shape> -> IQueryable<Shape> =
fun data -> data.Where(fun a -> a.y <> 2)
return Some filter2
else
return None
}
...and then composing these filters back and forth, e.g.
let data = [{x = 1; y = 1}; {x = 2; y = 2}].AsQueryable()
task {
let! getfilter1 = returnFilter "a"
let! getfilter2 = returnFilter "b"
match getfilter1, getfilter2 with
| Some filter1, Some filter2 ->
return data |> filter2 |> filter1 |> Seq.toList
// ...and so on...
Expected behavior
Silly me, by the code I expected the type of returnFilter is:
string -> Task<(IQueryable<Shape> -> IQueryable<Shape>) option>
Actual behavior
Well, the actual type of returnFilter is:
string -> Task<(#IQueryable<Shape> -> IQueryable<Shape>) option>
The code compiled well.
But this hash # made all the difference, because combining different returnFilters together caused them to be automatically upcasted to obj.
Thus the code threw at runtime:
System.Security.VerificationException: Method returnFilter: type argument 'System.Object' violates the constraint of type parameter 'a'.
at ...(my code location of "task{")...
at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.Start[TStateMachine](TStateMachine& stateMachine)
What I historically do is change the code from task to async and all is good, but not this time:
System.Security.VerificationException: Method returnFilter: type argument 'System.Object' violates the constraint of type parameter 'a'.
at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvoke[T,TResult](AsyncActivation`1 ctxt, TResult result1, FSharpFunc`2 part2) in D:\\a\\_work\\1\\s\\src\\FSharp.Core\\async.fs:line 510
at ...(mycode)...
at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in D:\\a\\_work\\1\\s\\src\\FSharp.Core\\async.fs:line 114
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at ...(mycode)...
Known workarounds
Do write the parameters explicitly.
You can even declare a type for your function:
type ShapeFilter = IQueryable<Shape> -> IQueryable<Shape>
let returnFilter condition : Task<Option<ShapeFilter>>=
//... and now the compiler is happy, and the runtime is happy.
There I was, minding my own business, of writing generic higher order functions in asynchronous code, when she hit me, the task-state-machine. She has hit me before, but this time I think she left some traces of her, so I could saw a glimpse of that monster. So I'll report it here, even this will probably be closed "as designed", it might help some other guy struggling in seduction by her.
I haven't been able to repro it in short code, yet, but this is what is going on. Observe the following code:
...and then composing these filters back and forth, e.g.
Expected behavior
Silly me, by the code I expected the type of
returnFilter
is:string -> Task<(IQueryable<Shape> -> IQueryable<Shape>) option>
Actual behavior
Well, the actual type of
returnFilter
is:string -> Task<(#IQueryable<Shape> -> IQueryable<Shape>) option>
The code compiled well. But this hash # made all the difference, because combining different returnFilters together caused them to be automatically upcasted to
obj
.Thus the code threw at runtime:
What I historically do is change the code from
task
toasync
and all is good, but not this time:Known workarounds
Do write the parameters explicitly. You can even declare a type for your function:
Related information
Provide any related information (optional):