检索代码引用中的数组项

Retrieving array item within code quotation

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

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

相信这可以通过使用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: 'Type mismatch when building 'args': invalid parameter for a method or indexer property. Expected 'System.Object[]', but received type 'System.Double[]'.

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

我显然误解了一些相当基本的东西!

感谢您提供的任何建议。

我认为这里的问题是你需要为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<double>MethodInfo,这是你需要的。

现有答案完美解决问题。我可以想到一个稍微简化的方法,即从最小引用中提取 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