Предположим, у меня есть функция F#, которая должна работать как локально, так и удаленно. Я хочу создать прокси функции, и пусть этот прокси решает, где запускать функцию, чтобы удаленный вызов был полностью невидим для внешнего мира. Если он будет работать локально, прокси просто вернет саму функцию, например:
let proxy_local f = f
Если нет, функция должна собрать все свои аргументы, затем отправить их по соединению и получить результат обратно. Вот тут-то и возникает сложность: мне нужно точно знать, какую функцию пользователь надеется передать через прокси, а также знать аргументы этой функции, чтобы я мог собрать их перед отправкой по сети.
Насколько я знаю, я не могу проверить сам аргумент функции, так как во время выполнения это будет какой-то неизвестный подкласс 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 |])
можно было заменить прямым вызовом функции?