1

在计算表达式上使用 Quote 成员将工作流转换为 AST,但希望它GetEnumerator()不会在构造引用时在序列上实际调用(即,具有某种形式的惰性)。在我的用例中,序列代表一个远程数据源,调用它的GetEnumerator()成员实际上会出去并查询它。

  1. 有没有办法在 Source 成员上隐式使用 Lazy 类型(并且仍然使用 Quote 成员),这样它就不会急切地调用GetEnumerator(),而是根本没有加载值?

  2. 为什么将 let 绑定定义为模块的属性和另一个函数中的变量在引号中被视为不同的实体,即PropertyGetvs Value

一些测试代码...

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()

这导致以下两个引用

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,
                                      []))))])
4

1 回答 1

2

有没有办法在 Source 成员上隐式使用 Lazy 类型(并且仍然使用 Quote 成员),这样它就不会急切地调用 GetEnumerator() 而是根本没有加载值?

我认为没有办法隐式使用 Lazy 类型。但是,我不太明白这个问题 - 当您使用该Quote方法时,您可以对获得的报价进行任何转换,因此您可以转换报价,使其实际上不调用GetEnumerator成员(当然,您将必须用其他返回数据的东西替换它......)

关键是构建查询不会调用GetEnumerator方法。因此,您应该能够在不调用GetEnumerator.

为什么将 let 绑定定义为模块的属性和另一个函数中的变量在引号中被视为不同的实体,即PropertyGetvs Value

模块中的 let 绑定被编译为静态成员,因此引用捕获了对该静态成员的引用。对于局部变量,无法捕获引用,因此Value节点将数据直接嵌入到引用中。(您可以通过实际从属性中获取当前值来编写转换PropertyGetValue

编辑:当我创建一个调用IEnumerable时抛出的内容时GetEnumerator,F# Interactive 中的打印引用显示错误(因为 F# Interactive 尝试评估序列以输出前几个成员),但引用仅包含源为Value.

如果您Run从构建器中删除该方法(以便它只返回引号),那么这应该可以工作(并返回“fancy source”字符串):

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
于 2013-08-23T23:18:05.770 回答