正在检索代码引号中的数组项



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

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

我相信这可以通过使用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看起来是一个泛型函数,我不清楚为什么会发生这种情况。

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

谢谢你能给我的任何建议。

现有的答案很好地解决了这个问题。我可以考虑一个稍微简化的方法,那就是从最小报价中提取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

我认为这里的问题是需要为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,这正是您所需要的。

最新更新