2016-11-25 48 views
1

我需要找出應用了函數的確切泛型參數的位置。
假設我有以下功能和類型我可以在MethodInfo對象上找到應用泛型參數的位置嗎?

type Box<'a, 'b> = Box of 'a * 'b 
let iToS<'a, 'b> (b : Box<'a, 'b>) : Box<string, 'b> = 
    let (Box (i,v)) = b 
    Box ((sprintf "%A" i), v) 

和我有得到一個函數,並返回相應的MethodInfo對象

let getMethodInfo f : MethodInfo = ... 
let mi = getMethodInfo <@ f @> 
//now query mi 

反正是有一個功能,找出其中通用參數是否適用?讓

let iToS<'a, 'b> (b : Box<'a, 'b>) : Box<string, 'b> = 
      | |   ^^    ^
      | |   | |     | 
      ----------------- |     | 
       |    |     | 
       ------------------------------------ 

所以給出一個泛型函數(及其相關的MethodInfo對象)和(潛在的通用)函數的參數和(潛在的通用)返回類型,
有沒有一種方法,如果通用PARAMS找出功能的被施加到功能PARAM類型和/或到函數返回類型如果任何的這些是一個通用的類型以及?

回答

4

您可以使用.NET反射來找出泛型參數出現在參數和返回類型的通用功能。

例如,讓我們來看看一個包裝爲Map.map

module Test = 
    let func (m:Map<'k, 'a>) (f:'k -> 'a -> 'b) : Map<'k, 'b> = Map.map f m 

爲了完整起見,這裏是你需要得到MethodInfo在F#互動的內容:

open System.Reflection 

let t = Assembly.GetExecutingAssembly().GetTypes() |> Seq.find (fun t -> t.Name = "Test") 
let mi = t.GetMethods() |> Seq.find (fun m -> m.Name = "func") 

的方法是通用的,您可以打印通用參數的名稱:

for a in mi.GetGenericArguments() do 
    printfn " - %s" a.Name 

下面的遞歸函數,然後將格式化可能包含泛型參數類型(它打印泛型參數的名稱,它也支持Map和功能,但沒有別的):

let rec formatType (typ:System.Type) = 
    if typ.IsGenericParameter then typ.Name 
    else 
    let args = 
     if typ.IsGenericType then 
     typ.GetGenericArguments() 
     |> Seq.map formatType |> List.ofSeq 
     else [] 
    match typ.Name, args with 
    | "FSharpFunc`2", [t1; t2] -> sprintf "(%s -> %s)" t1 t2 
    | "FSharpMap`2", [t1; t2] -> sprintf "Map<%s, %s>" t1 t2 
    | _ -> failwith "Not supported" 

如果再得到所有參數的方法的&返回類型:

[ for p in mi.GetParameters() do yield p.ParameterType 
    yield mi.ReturnType ] 
|> List.map formatType 
|> String.concat " -> " 

你得到的結果是類型簽名你在F#中看到:

val it:string = "Map<k, a> -> (k -> (a -> b)) -> Map<k, b>" 

此簽名包含所有通用參數。這是一個最小的例子,它不會將簽名中的名字與通用參數的名稱相匹配,但您可以輕鬆地按照以下示例執行此操作(只需檢查typ.IsGenericParameter返回true並使用其名稱將其與相應的名稱進行匹配的類型類型中其他地方的通用參數)。

0

實際上,如果您知道f#函數(表示爲靜態.NET方法的MethodInfo對象)也有一個方法GetGenericMethodDefinition其實很容易。 這似乎像f#中的typedefof< >運算符一樣工作,以獲得MethodInfo對象的通用定義。
鑑於該功能確實是一種通用功能,可以使用IsGenericMethodDefinition進行檢查。

下面一個SignaturePrinter,它與任何泛型函數運行任何類型的通用數據類型的

type Box<'a, 'b> = Box of 'a * 'b 

首先幾個幫手

let getFn e = 
    let rec name e = 
     match e with 
      | Call (x, mi, y) -> mi 
      | Lambda (_, body) -> name body 
      | _ -> failwith <| sprintf "not a function %A" e 
    name e 

let join sep (ss: string []) = String.Join(sep, ss) 

let wrap sepB sepE s = sprintf "%s%s%s" sepB s sepE 

let generics (ts:Type[]) = 
    if Array.length ts > 0 then 
     ts 
     |> Array.map (fun x -> x.Name) 
     |> join "," 
     |> wrap "<" ">" 
    else "" 

let genericDef (mi:MethodInfo) = mi.GetGenericMethodDefinition() 

然後簽名打印機使用自動報價

type SignaturePrinter = class end 
    with 
    static member Print ([<ReflectedDefinition>] f : Expr<'a -> 'b>) = 
     let mi = getFn f |> genericDef 
     let typeSig (t:Type) = 
      let sanitizedName (n:string) = n.Split('`') |> Array.head 
      [|sanitizedName t.Name; generics t.GenericTypeArguments|] 
      |> join "" 
     let fnParams (m:MethodInfo) = 
      m.GetParameters() 
      |> Array.map (fun x -> typeSig x.ParameterType) 
      |> join " -> " 

     [| 
      [|mi.Name; mi.GetGenericArguments() |> generics; " ::"|] |> join "" 
      fnParams mi 
      typeSig mi.ReturnType 
     |] 
     |> join " -> " 

一些通用的測試方法

let iToS<'x, 'y> (b : Box<'x, 'y>) : Box<string, 'y> = 
    let (Box (i,v)) = b 
    Box ((sprintf "%A" i), v) 
let iToB (Box (i:int, v:'a)) = Box (i > 0, v) 
let fixedRet (Box (i:int, v:'z)) = Box (true, false) 

並測試代碼

let s1 = SignaturePrinter.Print iToS 
//iToS<x,y> :: -> Box<x,y> -> Box<String,y> 
let s2 = SignaturePrinter.Print fixedRet 
//fixedRet<z> :: -> Box<Int32,z> -> Box<Boolean,Boolean> 
let s3 = SignaturePrinter.Print iToB 
//iToB<a> :: -> Box<Int32,a> -> Box<Boolean,a> 
相關問題