有没有办法在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”,我需要按名称查找该函数。
答案 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