2009-10-15 14 views
11

Chciałbym napisać funkcję, która przyjmuje funkcję f jako argument i zwraca System.Reflection.MethodInfo związane z f.Pobierz metodeInfo funkcji F #

Nie jestem do końca pewien, czy jest to wykonalne, czy nie.

+0

Co zamierzacie zrobić z MethodInfo? – Brian

+0

Próbuję uzyskać odzwierciedlenie definicji, z erm ..Funkcja TryGetReflectedDefinition. – Stringer

+1

Nie wiem nic w F # ale w o'caml można to zrobić za pomocą pre-procesora (nie wiem, czy jest coś podobnego w F #) http://groups.google.com/group/fa. caml/browse_thread/thread/25c9706b89196140 – LB40

Odpowiedz

5

W końcu znalazłem rozwiązanie. Bardzo hacky, ale hej! To działa! (edytuj: tylko w trybie debugowania).

let Foo (f:S -> A[] -> B[] -> C[] -> D[] -> unit) = 
    let ty  = f.GetType() 
    let argty = [|typeof<S>; typeof<A[]>; typeof<B[]>; typeof<C[]>;typeof<D[]>|] 
    let mi  = ty.GetMethod("Invoke", argty) 
    let il  = mi.GetMethodBody().GetILAsByteArray() 
    let offset = 9//mi.GetMethodBody().MaxStackSize 

    let token = System.BitConverter.ToInt32(il, offset)  
    let mb  = ty.Module.ResolveMethod(token) 

    match Expr.TryGetReflectedDefinition mb with 
    | Some ex -> printfn "success %A" e 
    | None -> failwith "failed" 

Działa dobrze, nawet jeśli f jest zdefiniowany w innym zestawie (.dll) lub w tym samym, w którym następuje wywołanie Foo. To nie jest w pełni ogólne, ponieważ muszę zdefiniować, co jest argty, ale jestem pewien, że mogę napisać funkcję, która to robi.

Po napisaniu tego kodu okazuje się, że Dustin ma podobne rozwiązanie dla tego samego problemu, aczkolwiek w języku C# (zobacz: here).

EDIT: Tak oto przykład użycia:

open System 
open Microsoft.FSharp.Quotations 

[<ReflectedDefinition>] 
let F (sv:int) (a:int[]) (b:int[]) (c:int[]) (d:int[]) = 
    let temp = a.[2] + b.[3] 
    c.[0] <- temp 
    () 

let Foo (f:S -> A[] -> B[] -> C[] -> D[] -> unit) = 
    let ty  = f.GetType() 
    let arr = ty.BaseType.GetGenericArguments() 
    let argty = Array.init (arr.Length-1) (fun i -> arr.[i]) 

    let mi  = ty.GetMethod("Invoke", argty) 
    let il  = mi.GetMethodBody().GetILAsByteArray() 
    let offset = 9 
    let token = System.BitConverter.ToInt32(il, offset) 
    let mb  = ty.Module.ResolveMethod(token) 
    mb 

let main() = 
    let mb = Foo F 
    printfn "%s" mb.Name 

    match Expr.TryGetReflectedDefinition mb with 
    | None ->() 
    | Some(e) -> printfn "%A" e 

do main() 

Co robi drukuje nazwę F i jego AST jeśli funkcja jest odzwierciedlenie definicji.

Ale po dalszym dochodzeniu, zdarza się, że ten hack, działa tylko w trybie debugowania (i F musi być wartością funkcji, a także definicji najwyższego poziomu), więc równie dobrze można powiedzieć, że jest to niemożliwe rzecz zrobić.

Oto kod IL o metodzie FSharpFunc powołują się zarówno w budowie debug/release:

Debug Mode: Tryb

.method /*06000007*/ public strict virtual 
     instance class [FSharp.Core/*23000002*/]Microsoft.FSharp.Core.Unit/*01000006*/ 
     Invoke(int32 sv, 
       int32[] a, 
       int32[] b, 
       int32[] c, 
       int32[] d) cil managed 
// SIG: 20 05 12 19 08 1D 08 1D 08 1D 08 1D 08 
{ 
    // Method begins at RVA 0x21e4 
    // Code size  16 (0x10) 
    .maxstack 9 
    IL_0000: /* 00 |     */ nop 
    IL_0001: /* 03 |     */ ldarg.1 
    IL_0002: /* 04 |     */ ldarg.2 
    IL_0003: /* 05 |     */ ldarg.3 
    IL_0004: /* 0E | 04    */ ldarg.s c 
    IL_0006: /* 0E | 05    */ ldarg.s d 
    IL_0008: /* 28 | (06)000001  */ call  void Program/*02000002*/::F(int32, 
                       int32[], 
                       int32[], 
                       int32[], 
                       int32[]) /* 06000001 */ 
    IL_000d: /* 00 |     */ nop 
    IL_000e: /* 14 |     */ ldnull 
    IL_000f: /* 2A |     */ ret 
} // end of method [email protected]::Invoke 

RELEASE:

method public strict virtual instance class [FSharp.Core]Microsoft.FSharp.Core.Unit 
     Invoke(int32 sv, 
       int32[] a, 
       int32[] b, 
       int32[] c, 
       int32[] d) cil managed 
{ 
    // Code size  28 (0x1c) 
    .maxstack 7 
    .locals init ([0] int32 V_0) 
    IL_0000: nop 
    IL_0001: ldarg.2 
    IL_0002: ldc.i4.2 
    IL_0003: ldelem  [mscorlib]System.Int32 
    IL_0008: ldarg.3 
    IL_0009: ldc.i4.3 
    IL_000a: ldelem  [mscorlib]System.Int32 
    IL_000f: add 
    IL_0010: stloc.0 
    IL_0011: ldarg.s c 
    IL_0013: ldc.i4.0 
    IL_0014: ldloc.0 
    IL_0015: stelem  [mscorlib]System.Int32 
    IL_001a: ldnull 
    IL_001b: ret 
} // end of method [email protected]::Invoke 

Można zobaczyć, że w wydaniu tryb kompilator dodaje kod F do metody Invoke, więc informacja o wywołaniu F (i możliwości odzyskania tokena) zniknęła ..

+0

Jeśli to działa dla Ciebie, będziesz chciał zaakceptować to jako odpowiedź. – kersny

+0

Czy możesz podać przykład użycia? Rozumiem ogólną ideę rozwiązania, ale nie rozumiem, dlaczego f ma taki typ, jaki ma. –

2

Nie jest to (łatwo) możliwe. Chodzi o to, aby pamiętać, że kiedy piszesz:

let printFunctionName f = 
    let mi = getMethodInfo f 
    printfn "%s" mi.Name 

parametru 'F' jest po prostu instancją typu FSharpFunc < ,>. Oto więc wszystko jest możliwe:

printFunctionName (fun x -> x + 1) // Lambda expression 
printFunctionName String.ToUpper  // Function value 
printFunctionName (List.map id)  // Curried function 
printFunctionNAme (not >> List.empty) // Function composition 

W obu przypadkach nie jest prosta odpowiedź na to

+0

Może to pomaga, wiem, że f jest zawsze wartością funkcji. Co polecasz? Wezmę każdy hack .. – Stringer

1

ja nie wiem, czy istnieje ogólna odpowiedź dla każdego rodzaju funkcji, ale jeśli funkcja jest proste ('a ->' b) następnie można napisać

let getMethodInfo (f : 'a -> 'b) = (FastFunc.ToConverter f).Method

+0

Dzięki, próbowałem go, ale nie wydaje się działać .. – Stringer

3

Czy program poniżej pomoc?

module Program 

[<ReflectedDefinition>] 
let F x = 
    x + 1 

let Main() = 
    let x = F 4  
    let a = System.Reflection.Assembly.GetExecutingAssembly() 
    let modu = a.GetType("Program") 
    let methodInfo = modu.GetMethod("F") 
    let reflDefnOpt = Microsoft.FSharp.Quotations.Expr.TryGetReflectedDefinition(methodInfo) 
    match reflDefnOpt with 
    | None -> printfn "failed" 
    | Some(e) -> printfn "success %A" e 

Main()  
+0

Tak, prawie tak, oczekuję, że nie znam nazwy metody ("F") lub modułu . – Stringer