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.
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?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
vsValue
.
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,
[]))))])
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 theGetEnumerator
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 callingGetEnumerator
.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 turnsPropertyGet
intoValue
by actually getting the current value from the property)EDIT: When I create an
IEnumerable
that throws whenGetEnumerator
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 asValue
.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):