【问题标题】:Retrieving array item within code quotation在代码引用中检索数组项
【发布时间】:2021-09-16 13:07:05
【问题描述】:

作为爱好项目的一部分,我想动态创建一个函数,当提供浮点数组时,它可以使用数组元素执行一些算术并返回结果。

请注意,该函数必须在运行时创建。

相信这可以通过使用 FSharp.Quotations.Expr 模块构造一个 lambda 表达式来完成。

举个简单的例子:

fun (arr: float array) -> 2.0 * arr.[0]

我可以使用以下代码引用重新创建它:

<@ fun (arr: float array) -> 2.0 * arr.[0] @>

将其打印到控制台会产生:

Lambda (arr,
        Call (None, op_Multiply,
              [Value (2.0), Call (None, GetArray, [arr, Value (0)])]))

我可以看到提到的GetArray 是位于FSharp.Core.LanguagePrimitives.IntrinsicFunctions.GetArray 的那个。

我的问题是... 如何在运行时创建Call (None, GetArray, [arr, Value (0)])


到目前为止,我的(令人尴尬的)尝试是:

open System
open FSharp.Quotations

let arr = Var("arr", typeof<float array>, false)

let getArray = FSharp.Core.LanguagePrimitives.IntrinsicFunctions.GetArray.GetType().GetMethods().[0]

Expr.Lambda(arr, Expr.Call(getArray, [Expr.Var(arr); Expr.Value(0)]))

这会产生以下异常:

System.ArgumentException:“构建“args”时的类型不匹配:方法或索引器属性的参数无效。应为“System.Object[]”,但收到类型“System.Double[]”。

鉴于GetArray 看起来是一个通用函数,我不清楚为什么会这样。

我显然误解了这里相当基本的东西!

感谢您提供的任何建议。

【问题讨论】:

    标签: f# metaprogramming


    【解决方案1】:

    现有的答案完美地解决了这个问题。我可以想到一个稍微的简化,那就是从最小的引用中提取GetArray 方法信息,这样您就不必担心使用反射手动找到方法。

    open Microsoft.FSharp.Quotations
    
    let arr = Var("arr", typeof<float array>, false)
    let getArray<'T> = match <@ ([||] : 'T[]).[0] @> with Patterns.Call(_, mi, _) -> mi | _ -> failwith "Array access was not Call"
    let expr = Expr.Lambda(arr, Expr.Call(getArray<float>, [Expr.Var(arr); Expr.Value(0)]))
    printfn "%A" expr
    

    如果方法被移动,这可能是一个更未来的证明,但它仍然只有在数组访问被公开为方法调用时才有效。

    也许一种更有趣的方法是使用引号拼接,它可以让您将构建数组访问的位分隔到一个函数中,但将其写为引号:

    let arr = Var("arr", typeof<float array>, false)
    
    let getArray (e:Expr<'T[]>) (a:Expr<int>) = 
      <@ (%e).[%a] @>
    
    let expr = 
      Expr.Lambda(arr, 
        getArray (Expr.Cast<float array>(Expr.Var(arr))) 
          (Expr.Cast<int>(Expr.Value(0))))
    
    printfn "%A" expr
    

    【讨论】:

    • 感谢 Tomas... 我同意这些都是非常优雅的选择!特别是,您的第二种方法特别令人印象深刻,根本不需要反思(如果我理解正确的话?)。这绝对是使用引用的有用见解。
    • 是的,第二种方法避免了反射的需要——唯一棘手的是你需要将数组的类型作为静态类型(这样你就可以调用getArray正确的类型参数。如果这是运行时信息,则它不起作用(您可以使其工作,但通过不同的反射黑客)。
    • 明白。并感谢您的确认!
    • 只是说...我使用第二种方法来创建引号拼接器以进行简单的加法和乘法运算。像魅力一样工作! (Gist)。
    • @RMills330 不错!
    【解决方案2】:

    我认为这里的问题是您需要为GetArray 本身获取MethodInfo,这需要反射。我不确定是否有一种简单的方法可以做到这一点,但以下方法似乎可行:

    let arr = Var("arr", typeof<float array>, false)
    let getArray =
        let asm = System.Reflection.Assembly.Load("FSharp.Core")
        let typ = asm.DefinedTypes |> Seq.find (fun typ -> typ.Name = "IntrinsicFunctions")
        let getArrayGeneric = typ.GetMethod("GetArray")
        getArrayGeneric.MakeGenericMethod(typeof<double>)
    let expr = Expr.Lambda(arr, Expr.Call(getArray, [Expr.Var(arr); Expr.Value(0)]))
    printfn "%A" expr   // Lambda (arr, Call (None, GetArray, [arr, Value (0)]))
    

    这会在FSharp.Core 程序集中找到通用的GetArray 方法,然后用double 对其进行实例化,以获得GetArray&lt;double&gt;MethodInfo,这正是您所需要的。

    【讨论】:

    • 感谢您查看...但是,上面提议的更改给出了一个不同的例外:The member is non-static (instance), but no receiver object was supplied Arg_ParamName_Name。请注意,我使用....GetMethods().[0] 来尝试获取Invoke(...) 方法。但是,我现在想知道这是否是一个非静态成员,因此是例外?
    • 是的,我看到了同样的事情并正在调查它。表情很挑剔。 :) 如果我弄明白了,会更新答案。
    • 我有一些似乎可行的方法,并相应地更新了我的答案。
    • 完美!感谢@brianberns。根据我的知识,我不会想到使用上述方法获取 GetArray 的 MethodInfo。您提供的内容允许我在运行时重新创建该引用,这正是我想要的。
    猜你喜欢
    • 2014-01-30
    • 1970-01-01
    • 2021-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多