2014-02-20 104 views
6

有沒有辦法在F#中按名稱調用函數?給定一個字符串,我想從全局命名空間(或者一般來說,給定的模塊)中獲取一個函數值,然後調用它。我知道這個功能的類型了。我可以在f#中通過名稱調用函數嗎?

爲什麼我要這樣做?我正在努力解決fsi沒有--eval選項。我有一個腳本文件,它定義了許多int - >()函數,並且我想執行其中的一個。像這樣:

fsianycpu --use:script_with_many_funcs.fsx --eval "analyzeDataSet 1" 

我的想法是寫一個腳本蹦牀,如:

fsianycpu --use:script_with_many_funcs.fsx trampoline.fsx analyzeDataSet 1 

爲了寫「trampoline.fsx」,我需要通過名稱來查找功能。

回答

6

這裏沒有內置函數,但是可以使用.NET反射來實現它。這個想法是搜索當前程序集中可用的所有類型(這是編譯當前代碼的地方),並動態調用具有匹配名稱的方法。如果你在模塊中有這個,你也必須檢查類型名稱。

// Some sample functions that we might want to call 
let hello() = 
    printfn "Hello world" 

let bye() = 
    printfn "Bye" 

// Loader script that calls function by name 
open System 
open System.Reflection 

let callFunction name = 
    let asm = Assembly.GetExecutingAssembly() 
    for t in asm.GetTypes() do 
    for m in t.GetMethods() do 
     if m.IsStatic && m.Name = name then 
     m.Invoke(null, [||]) |> ignore 

// Use the first command line argument (after -- in the fsi call below) 
callFunction fsi.CommandLineArgs.[1] 

這時候通過所謂運行的Hello World:

fsi --use:C:\temp\test.fsx --exec -- "hello" 
+0

謝謝,這對我的作品。 @ jbtule的答案看起來更復雜,但也可能更加健壯。當簡單的答案有效時,我喜歡簡單的答案。 :-) –

+1

在交互式場景中,當每個部分編譯向正在執行的程序集中添加一個類型和一個名稱相同的靜態方法時,好的簡單答案將會消失。以及如何區分類型上的let-bound函數和靜態成員? – kaefer

3

可以使用反射通過FSharp功能名稱來獲得功能MethodInfo

open System 
open System.Reflection 

let rec fsharpName (mi:MemberInfo) = 
    if mi.DeclaringType.IsNestedPublic then 
     sprintf "%s.%s" (fsharpName mi.DeclaringType) mi.Name 
    else 
     mi.Name 

let functionsByName = 
     Assembly.GetExecutingAssembly().GetTypes() 
       |> Seq.filter (fun t -> t.IsPublic || t.IsNestedPublic) 
       |> Seq.collect (fun t -> t.GetMethods(BindingFlags.Static ||| BindingFlags.Public)) 
       |> Seq.filter (fun m -> not m.IsSpecialName) 
       |> Seq.groupBy (fun m -> fsharpName m) 
       |> Map.ofSeq 
       |> Map.map (fun k v -> Seq.exactlyOne v) 

然後,您可以調用MethodInfo

functionsByName.[fsharpFunctionNameString].Invoke(null, objectArrayOfArguments) 

但是,您可能需要做更多的工作來使用MethodInfo.GetParameters()類型作爲提示來解析字符串參數。

1

你也可以使用FSharp.Compiler.Service,使自己的fsi.exe有一個eval標誌

open System 
open Microsoft.FSharp.Compiler.Interactive.Shell 
open System.Text.RegularExpressions 

[<EntryPoint>] 
let main(argv) = 

    let argAll = Array.append [| "C:\\fsi.exe" |] argv 
    let argFix = argAll |> Array.map (fun a -> if a.StartsWith("--eval:") then "--noninteractive" else a) 
    let optFind = argv |> Seq.tryFind (fun a -> a.StartsWith "--eval:") 
    let evalData = if optFind.IsSome then 
         optFind.Value.Replace("--eval:",String.Empty) 
        else 
         String.Empty 
    let fsiConfig = FsiEvaluationSession.GetDefaultConfiguration() 
    let fsiSession = FsiEvaluationSession(fsiConfig, argFix, Console.In, Console.Out, Console.Error) 
    if String.IsNullOrWhiteSpace(evalData) then 
     fsiSession.Run() 
    else 
     fsiSession.EvalInteraction(evalData) 
    0 

如果上述被編譯成fsieval.exe它可以作爲這樣

fsieval.exe --load:script_with_many_funcs.fsx --eval:analyzeDataSet` 1 
相關問題