reflection - 可以在运行时专门化 F# 函数吗?
问题描述
假设我有一个应该在本地和远程运行的 F# 函数。我想创建一个函数的代理,让那个代理决定在哪里运行函数,这样远程调用对外界是完全不可见的。如果它要在本地运行,代理只会返回函数本身,如下所示:
let proxy_local f = f
如果不是,该函数必须收集其所有参数,然后通过连接发送它们并返回结果。这就是困难的地方:我需要确切地知道用户希望代理哪个函数,还需要知道该函数的参数,这样我就可以在通过网络发送它们之前收集它们。
AFAIK 我无法检查函数参数本身,因为在运行时它将是一些未知的子类,FSharpFunc
它收集参数并用它们调用函数。为了解决这个问题,我想我需要使用引用(顺便说一句,有没有更好的方法来做这部分?):
let getMethodInfo = function
| Call (_, mi, _) -> [], mi
| Lambdas (vs, Call(_, mi, _)) -> List.map (fun (v: Var list) -> (List.head v).Type) vs, mi
| _ -> failwith "Not a function"
let proxy_remote (f: Expr) =
let (argTypes, methodInfo) = getMethodInfo f
… ? // What to return?
let proxy f = if isLocal then proxy_local f else proxy_remote f
getMethodInfo
上面的方法不适用于带有元组参数的方法,我们也需要修改proxy_local
,但现在让我们把它放在一边。问题是 的返回值proxy_remote
。它应该是一个与原始参数相同的函数,但它应该在最后通过网络发送它们。像这样的东西:
let callRemote (method: MethodInfo) a b c … = doHttpConnection()
但是,需要输入参数。这是真正的问题:由于 F# 函数调用在编译时被转换为 FSharpFunc 子类,因此我不知道如何获得可以与反射一起使用的非专业表示。我真正的问题是:F# 函数可以在运行时使用未知类型进行专门化吗?
我可以想到两种方法来解决这个问题。第一个是proxy
本身是通用的:
let call1<'p, 'res> (method: MethodInfo) (p: 'p) = method.Invoke(null, [| p :> obj |]) :?> 'res
let call2<'p1, 'p2, 'res> (method: MethodInfo) (p1: 'p1) (p2: 'p2) = method.Invoke(null, [| p1 :> obj; p2 :> obj |]) :?> 'res
…
let proxy1 (f: Expr<'a -> 'b>) (s: string) : 'a -> 'b =
let (types, mi) = getMethodInfo f
match types with
| [_] -> call1<'a, 'b> mi
| _ -> failwith ""
let proxy2 (f: Expr<'a -> 'b -> 'c>) : 'a -> 'b -> 'c =
let (types, mi) = getMethodInfo f
match types with
| [_; _] -> call2<'a, 'b, 'c> mi
| _ -> failwith ""
…
这当然可以,但它需要程序员提前考虑每个函数的输入数量。更糟糕的是,具有更多参数的函数将适用于所有接受较少参数的代理方法:
let f a b = a + b
let fproxy = proxy1 f // The result will be of type int -> (int -> int), not what we want at all!
另一种方法是为此目的创建 FSharpFunc 的特殊子类:
type call1<'a, 'res>(id, ps) =
inherit FSharpFunc<'a, 'res>()
override __.Invoke(x: 'a) = callImpl<'res> id ((x :> obj) :: ps)
type call2<'a, 'b, 'res>(id, ps) =
inherit FSharpFunc<'a, FSharpFunc<'b, 'res>>()
override __.Invoke(x) = call1<'b, 'res>(id, x :> obj :: ps) :> obj :?> FSharpFunc<'b, 'res>
…
let proxy (f: Expr<'a -> 'b>) : 'a -> 'b =
let (types, methodInfo) = getMethodInfo f
match types with
| [a] ->
let t = typedefof<call1<_,_>>.MakeGenericType([| a; methodInfo.ReturnType |])
t.GetConstructors().[0].Invoke([|methodInfo; []|]) :?> ('a -> 'b)
| [a; b] ->
let t = typedefof<call2<_,_,_>>.MakeGenericType([| a; b; methodInfo.ReturnType |])
t.GetConstructors().[0].Invoke([|methodInfo; []|]) :?> ('a -> 'b)
…
| _ -> failwith ""
它会起作用,但它可能不会像 F# 编译器生成的那样高效,特别是在所有动态转换的情况下。那么,有没有办法在运行时专门化 F# 函数,所以typedefof<call1<_,_>>.MakeGenericType([| a; methodInfo.ReturnType |])
可以用直接调用函数来代替?
解决方案
经过大量搜索,似乎没有办法做我想做的事。然而,我确实了解到,其他人也将 FSharpFunc 子类化为一些非常奇怪的用例,所以它并非完全闻所未闻。如果有人有一些见解可以分享,我会留下这个问题。
推荐阅读
- html - 提供容器图像大小的图像的 CSS
- unity3d - 碰撞后如何重生?(Unity2D)
- javascript - 使用 react-bootstrap-table-next 按默认降序对最后更新的条目进行排序
- flutter - 如何从列表视图中删除某些内容并重新加载页面?颤振网络
- javascript - expo 弹出后 ios 文件夹出现问题
- php - 按字母顺序分组数组
- regex - 将捕获组命名为不同的名称
- html - 当表格容器具有溢出-x 自动时,工具提示被切断
- javascript - 如何使输入+文本可通过同一行单击/显示为列表
- python - 从具有相同结构的字符串中分离不同的名称