2014-02-20 102 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 
相关问题