Question

Using the Quote member on a computation expression to convert the workflow into an AST, but would like it be such that the GetEnumerator() is not actually called on the sequence as the quotation is constructed (i.e., have some form of laziness). In my use case, the sequence represents a remote data-source, and invoking the GetEnumerator() member on it will actually go out and query against it.

  1. Is there some way to implicitly use the Lazy type (and still use the Quote member) on the Source member so that it does not eagerly call GetEnumerator() and instead has simply not loaded the value yet?

  2. Why is a let binding defined as a property of a module and a variable within another function treated as different entities in the quotation, i.e. PropertyGet vs Value.

Some test code...

module Example

    open Microsoft.FSharp.Quotations

    [<Interface>]
    type I<'Type> =
        inherit seq<'Type>

    type FooBuilder() =

        member __.Source (x : #seq<'Type>) : I<'Type> = invalidOp "not implemented"

        member __.For (source : I<'Type>, f : 'Type -> I<'Type>) : I<'Type> = invalidOp "not implemented"

        member __.Zero () : I<'Type> = invalidOp "not implemented"

        member __.Quote (expr : Expr<#seq<'Type>>) = expr

        member __.Run (expr : Expr<#seq<'Type>>) =
            System.Console.WriteLine(expr)

    let foo = FooBuilder()

    let bar = [1; 2; 3]
    foo {
        for x in bar do ()
    }

    let insideLet() =
        let bar = [1; 2; 3]
        foo {
            for x in bar do ()
        }

    insideLet()

which results in the following two quotations

Call (Some (Value (FSI_0009+FooBuilder)), For,
      [Call (Some (Value (FSI_0009+FooBuilder)), Source,
             [PropertyGet (None, bar, [])]),
       Lambda (_arg1,
               Let (x, _arg1,
                    Sequential (Value (<null>),
                                Call (Some (Value (FSI_0009+FooBuilder)), Zero,
                                      []))))])

Call (Some (Value (FSI_0009+FooBuilder)), For,
      [Call (Some (Value (FSI_0009+FooBuilder)), Source, [Value ([1; 2; 3])]),
       Lambda (_arg1,
               Let (x, _arg1,
                    Sequential (Value (<null>),
                                Call (Some (Value (FSI_0009+FooBuilder)), Zero,
                                      []))))])
Was it helpful?

Solution

Is there some way to implicitly use the Lazy type (and still use the Quote member) on the Source member so that it does not eagerly call GetEnumerator() and instead has simply not loaded the value yet?

I don't think there is a way to implicitly use the Lazy type. However, I do not quite understand the question - when you use the Quote method, you can perform any transformation on the quotation that you get, so you can transform the quotation so that it does not actually call the GetEnumerator member (of course, you'll have to replace it wiht something else that returns the data...)

The key thing is that building the query does not call the GetEnumerator method. So you should be able to get the quotation & transform it & evaluate it without calling GetEnumerator.

Why is a let binding defined as a property of a module and a variable within another function treated as different entities in the quotation, i.e. PropertyGet vs Value.

A let binding in a module is compiled into a static member and so the quotation captures a reference to this static member. For local variables, capturing references is not possible, so the Value node embeds the data directly into the quotation. (You could write a transformation that turns PropertyGet into Value by actually getting the current value from the property)

EDIT: When I create an IEnumerable that throws when GetEnumerator is called, then the printed quotation in F# interactive shows the error (because F# interactive tries to evaluate the sequence to output first few members), but the quotation just contains the source as Value.

If you remove the Run method from your builder (so that it just returns the quotation), then this should work (and return the "fancy source" string):

type FancySource() = 
  member x.Source = "fancy source"
  interface seq<int> with
    member x.GetEnumerator() = failwith "!" 
  interface System.Collections.IEnumerable with
    member x.GetEnumerator() = failwith "!" 

let insideLet() =
  let bar = new FancySource()
  foo { for x in bar do () }

match insideLet() with
| Patterns.Call(_, _, Patterns.Call(_, _, [Patterns.Value(:? FancySource as v, _)])::_) -> 
    v.Source
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top