f# - 从 F# 引用转换为 Linq 表达式时出现 InvalidOperationException

标签 f# quotations

我正在尝试替换 F# Expr 中的类型,然后将其转换为 Expression供 C# 库使用。 但调用LeafExpressionConverter.QuotationToExpression后我收到错误

InvalidOperationException: The variable 't' was not found in the translation context

基本上我正在尝试替代

<@ fun (t: Record) -> t.A = 10 @>

<@ fun (t: Dict) -> t["A"] = 10 @>

这是代码

type Record = {
    A: int
}
type Dict () = //this is the type the c# lib wants (a dictionary representation of a type)
    inherit Dictionary<string, obj>()

let substitute<'a> (ex: Expr<'a->bool>) = 
    let replaceVar (v: Var) = if v.Type = typeof<'a> then Var(v.Name, typeof<Dict>) else v
    let tEntityItem = typeof<Dict>.GetProperty("Item")
    let isATypeShapeVar = function | ShapeVar var -> var.Type = typeof<'a> | _ -> false
    let rec substituteExpr = 
        function
        | PropertyGet(exOpt, propOrValInfo, c) -> 
            match exOpt with
                | None -> Expr.PropertyGet(propOrValInfo)
                | Some ex -> 
                    let args = c |> List.map substituteExpr
                    let newex = substituteExpr ex
                    match isATypeShapeVar ex with
                    | true -> 
                        let getter = Expr.PropertyGet(newex, tEntityItem, [Expr.Value(propOrValInfo.Name)] )
                        Expr.Coerce(getter, propOrValInfo.PropertyType)
                    | false -> Expr.PropertyGet(newex, propOrValInfo, args)
        | ShapeVar var -> Expr.Var (var |> replaceVar)
        | ShapeLambda (var, expr) -> Expr.Lambda(var |> replaceVar, substituteExpr expr)
        | ShapeCombination(shapeComboObject, exprList) ->
            RebuildShapeCombination(shapeComboObject, List.map substituteExpr exprList) 
        substituteExpr ex |> LeafExpressionConverter.QuotationToExpression

substitute<Record> (<@ fun t -> t.A = 10 @>)

我怀疑我在替换过程中遗漏了一些东西,但我不知道是什么。

.ToString()替换 F# 的结果 Expr

Lambda (t, Call (None, op_Equality, [Coerce (PropertyGet (Some (t), Item, [Value ("A")]), Int32), Value (10)]))

看起来是正确的。除了强制之外,相当于 <@ fun (t: Dict) -> t["A"] = 10 @>.ToString()

为什么是QuotationToExpression失败?

最佳答案

每次调用 replaceVar 时,都会返回 Var 的不同实例。因此,当您替换 lambda 参数时,它是 Var 的一个实例,稍后,当您替换 newex 时,它是 Var 的另一个实例。

Lambda (t, Call (None, op_Equality, [Coerce (PropertyGet (Some (t), ... ))
        ^                                                       ^
        |                                                       |
        ---------------------------------------------------------
        These are different `t`, unrelated, despite the same name

要使其正常工作,您必须使其与t相同。最愚蠢、最直接的方法是:

let substitute<'a> (ex: Expr<'a->bool>) =
    let newArg = Var("arg", typeof<Dict>)
    let replaceVar (v: Var) = if v.Type = typeof<'a> then newArg else v
    ...

这将使您的特定示例按预期工作,但它仍然不健全,因为您不仅要替换特定的 lambda 参数,还要替换任何相同类型的变量。这意味着如果表达式碰巧包含与参数类型相同的任何变量,您仍然会遇到同样的问题。例如,尝试转换此:

<@ fun t -> let z = { A = 15 } in z.A = 15 && t.A = 10 @>

您会收到类似的错误,但这次提示变量 z

更好的方法是随时维护变量替换映射,在第一次遇到新变量时插入新变量,但在后续遇到时从映射中获取它们。


另一种方法是专门找出 lambda 参数,然后仅替换它,而不是比较变量类型。


但是还有下一个奇怪的层次:您将任何属性访问器转换为索引器访问器,但在上面的示例中,z.A 不应该被这样转换。因此,您必须以某种方式识别属性访问的对象实际上是否是参数,这可能并不那么微不足道。

如果您愿意满足于 t.A 的情况,而在 (if true then t else t).A 等更复杂的情况下失败,那么您可以只匹配 lambda 参数并传递任何其他表达式:

let substitute<'a> (ex: Expr<'a->bool>) =
    let arg = 
          match ex with 
          | ShapeLambda (v, _) -> v
          | _ -> failwith "This is not a lambda. Shouldn't happen."

    let newArg = Var("arg", typeof<Dict>)
    let replaceVar (v: Var) = if v = arg then newArg else v

    let tEntityItem = typeof<Dict>.GetProperty("Item")
    let isATypeShapeVar = function | ShapeVar var -> var.Type = typeof<'a> | _ -> false
    let rec substituteExpr =
        function
        | PropertyGet(Some (ShapeVar a), propOrValInfo, c) when a = arg ->
            let getter = Expr.PropertyGet(Expr.Var newArg, tEntityItem, [Expr.Value(propOrValInfo.Name)] )
            Expr.Coerce(getter, propOrValInfo.PropertyType)
        | ShapeVar var -> Expr.Var (var |> replaceVar)
        | ShapeLambda (var, expr) -> Expr.Lambda(var |> replaceVar, substituteExpr expr)
        | ShapeCombination(shapeComboObject, exprList) ->
            RebuildShapeCombination(shapeComboObject, List.map substituteExpr exprList)
        | ex -> ex

    substituteExpr ex |> LeafExpressionConverter.QuotationToExpression

> substituteExpr <@ fun t -> let z = { A = 15 } in z.A = 15 && t.A = 10 @>

val it: System.Linq.Expressions.Expression =
  ToFSharpFunc(arg => z => ((z.A == 15) AndAlso (Convert(arg.get_Item("A"), Int32) == 10)).Invoke(new Record(15)))

关于f# - 从 F# 引用转换为 Linq 表达式时出现 InvalidOperationException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70373497/

相关文章:

.net - FsCheck 和 NUnit 集成

javascript - 使用附加时删除引号或包含引号

sql - 如何用单引号插入文本sql server 2005

jQuery 随机 block 引用

.net - 奇怪的行为 XmlDocument.LoadXML 和 GetElementByID,如何声明带引号的字符串

代码示例的 Python 查询

function - 如何将此函数转换为使用尾调用

silverlight - 使用 F# 创建 Silverlight 3 应用程序

c# - 如何将内部构件导出到 F# 中的测试项目?

F#:消除 Map/Reduce/Filter 中的冗余