【问题标题】:F# laziness in quoted computation expressions引用计算表达式中的 F# 惰性
【发布时间】:2013-08-23 23:05:20
【问题描述】:

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

  1. 有什么方法可以隐式使用 Lazy 类型(并且仍然使用 Quote 成员) Source 成员,这样它就不会急切地调用GetEnumerator(),而是有 只是还没有加载值?

  2. 为什么将 let 绑定定义为模块的属性和另一个模块中的变量 函数在引用中被视为不同的实体,即PropertyGet vs 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,
                                      []))))])

【问题讨论】:

    标签: f# f#-3.0 computation-expression


    【解决方案1】:

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

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

    关键是构建查询不会调用GetEnumerator 方法。因此,您应该能够在不调用GetEnumerator 的情况下获取报价并对其进行转换和评估。

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

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

    编辑:当我创建一个在调用GetEnumerator 时抛出的IEnumerable 时,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
    

    【讨论】:

    • 在对 Source 的调用中,参数是一个包含值的单例列表。在我的示例中,它是列表 [1; 2; 3],但是对于我的远程数据源,它是结果的序列。所以GetEnumerator() 在内部以某种方式被调用以生成它。
    • @user2712561 我认为它实际上并没有调用GetEnumerator.. 查看编辑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-16
    相关资源
    最近更新 更多