我可以在f#中按名称调用函数吗?

时间:2014-02-20 13:37:45

标签: f#

有没有办法在F#中按名称调用函数?给定一个字符串,我想从全局命名空间(或者,通常是给定的模块)中获取一个函数值,然后调用它。我已经知道了这个功能的类型。

为什么我要这样做?我正在努力解决没有--eval选项的fsi问题。我有一个脚本文件,定义了许多int->()函数,我想执行其中一个。像这样:

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

我的想法是编写一个蹦床脚本,如:

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

为了编写“trampoline.fsx”,我需要按名称查找该函数。

3 个答案:

答案 0 :(得分: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"

答案 1 :(得分: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()类型作为提示来解析字符串参数。

答案 2 :(得分:1)

您还可以使用FSharp.Compiler.Service制作带有评估标记的fsi.exe

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