Why does this F# quotation fail at run-time?

169 Views Asked by At

I am trying to understand why this F# script fails at run-time.

#r "nuget: FsToolkit.ErrorHandling, 2.2.0"

open System
open FsToolkit.ErrorHandling

<@@ async {
        return!
          asyncResult {
            return! (Result.Ok true)
          }
} @@>

The error:

$ dotnet fsi ./Quotations.fsx

System.InvalidOperationException: Could not bind function AsyncResultBuilder.Source in type FsToolkit.ErrorHandling.AsyncResultCEExtensions
   at Microsoft.FSharp.Quotations.PatternsModule.fail@1118[a](Type ty, String nm, Unit unitVar0) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\quotations.fs:line 1118
   at Microsoft.FSharp.Quotations.PatternsModule.bindModuleFunctionWithCallSiteArgs$cont@1110(Type ty, String nm, Type[] argTypes, Type[] tyArgs, Unit unitVar) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\quotations.fs:line 1169
   at Microsoft.FSharp.Quotations.PatternsModule.bindModuleFunctionWithCallSiteArgs(Type ty, String nm, FSharpList`1 argTypes, FSharpList`1 tyArgs) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\quotations.fs:line 1105
   at Microsoft.FSharp.Quotations.PatternsModule.u_ModuleDefn@1572-1.Invoke(FSharpList`1 argTypes, FSharpList`1 tyargs) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\quotations.fs:line 1576
   at Microsoft.FSharp.Quotations.PatternsModule.u_constSpec@1636.Invoke(FSharpList`1 argTypes, FSharpList`1 tyargs) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\quotations.fs:line 1636
   at [email protected](BindingEnv env) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\quotations.fs:line 1501
   at Microsoft.FSharp.Primitives.Basics.List.mapToFreshConsTail[a,b](FSharpList`1 cons, FSharpFunc`2 f, FSharpList`1 x) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\local.fs:line 239
   at Microsoft.FSharp.Primitives.Basics.List.map[T,TResult](FSharpFunc`2 mapping, FSharpList`1 x) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\local.fs:line 247
   at [email protected](BindingEnv env) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\quotations.fs:line 1493
   at [email protected](BindingEnv env) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\quotations.fs:line 1508
   at Microsoft.FSharp.Primitives.Basics.List.mapToFreshConsTail[a,b](FSharpList`1 cons, FSharpFunc`2 f, FSharpList`1 x) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\local.fs:line 239
   at Microsoft.FSharp.Primitives.Basics.List.map[T,TResult](FSharpFunc`2 mapping, FSharpList`1 x) in F:\workspace\_work\1\s\src\fsharp\FSharp.Core\local.fs:line 247
...

Is this an issue with F# quotations? Or the FsToolkit.ErrorHandling package?

Please explain the error message and why it occurs.

3

There are 3 best solutions below

0
On

I think the short answer is a combination of both. Here's what I've been able to determine:

  • The F# compiler desugars your asyncResult expression into a call to AsyncResultCEExtensions.Source. See explanation here and source code here.
  • There are several different overloads of the Source method, so the quotation mechanism has some complicated logic to try to figure out which one to pick. See source code here.
  • Unfortunately, the quotation mechanism can't figure out which Source method the compiler has in mind, so it fails with the error message you pasted: Could not bind function AsyncResultBuilder.Source in type FsToolkit.ErrorHandling.AsyncResultCEExtensions.

One might consider this a bug in the quotation mechanism, since it failed to understand the compiler's intent. However, I'm not sure if F# guarantees that every possible legal expression is quotable (hence the semi-friendly error message you encountered). Someone with more knowledge of the F# compiler and core library internals might be able to shine more light.

0
On

As was stated by answer above, it possibly happens due to overload resolution in quotations. But you can work around that by wrapping builder in a function which is then passed to a quotation:

let returnResult =
  asyncResult {
    return! (Result.Ok true)
  }
      
let quotation =
  <@@
    async {
      return! returnResult
    }
  @@>
  
...

Here returnResult will be a ValueOp inside of ValueWithName returning the returnResult FSharpFunc.

0
On

This works for me (notice I'm using return and not return!. I'm guessing that the type inference needs more information but I will not to investigate further.

<@@ async {
    return!
      asyncResult {
        return (Result.Ok true)
      }
} @@>

Update

After further investigation it appears there may be a bug. I opened a new issue System.InvalidOperationException - Could not bind function *** in type

The problem seems to be the computation expression library using a combination of things -

  • Dividing builder overloads into separate modules
  • Overloading the Source member (as mentioned in other answers)
  • Using a weakly typed Result<bool, _> (the whole needs to be filled)
  • Other Source member overloads that are not well typed i.e. Source (s:#seq<_>)
  • Evaluation inside a quotation

Any change in one of these solves the problem.