Могу ли я вызвать функцию по имени в 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", мне нужно будет найти функцию по имени.

3 ответов


для этого нет встроенной функции, но вы можете реализовать ее с помощью .NET reflection. Идея состоит в том, чтобы искать все типы, доступные в текущей сборке (здесь компилируется текущий код), и динамически вызывать метод с соответствующим именем. Если бы у вас было это в модуле, вам также пришлось бы проверить имя типа.

// 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"

вы можете использовать отражение, чтобы получить функции как MethodInfoпо имени функции FSharp

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() типы в качестве подсказки.


вы также можете использовать FSharp.Компилятор.Сервис сделать свой собственный 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