在f#中自动计算出函数名称

bst*_*ack 7 logging f# functional-programming

如果我有一个函数是模块的一部分,并且我想在函数内部进行日志条目,我必须手动打印函数名称空间和名称,例如

namespace MyModuleNamespace
 module MyModule = 
 let AddTwoNums logger x y =
    logger.Info("MyModuleNamespace.AddTwoNums - Start")
    let res = x+y
    logger.Info("MyModuleNamespace.AddTwoNums - End")
    res
Run Code Online (Sandbox Code Playgroud)

有什么方法可以自动解决"MyModuleNamespace.AddTwoNums"的问题,因为它非常麻烦/容易出错,尤其是在重构代码时必须重命名函数和模块时

即使无法做到,有什么方法可以自动解决"AddTwoNums"是什么,即功能名称?

Aar*_*ach 7

有几种方法可以做到这一点,我不确定哪种方法最适合您的方案.我过去使用的一种方法是从堆栈跟踪中获取信息:

let stackTrace = StackTrace()
let topFrame = stackTrace.GetFrame(0)
let currentFunction = topFrame.GetMethod()
printfn "%s.%s" currentFunction.DeclaringType.Name currentFunction.Name
Run Code Online (Sandbox Code Playgroud)

为了不必将这些行放在每个函数中,你可以创建一个inline执行此操作的函数,它将为您提供由于内联而调用函数的名称.

let inline getCurrentFunction () =
    let stackTrace = StackTrace()
    let topFrame = stackTrace.GetFrame(0)
    let currentFunction = topFrame.GetMethod()
    sprintf "%s.%s" currentFunction.DeclaringType.Name currentFunction.Name
Run Code Online (Sandbox Code Playgroud)

  • 确实值得注意的是,这是获取函数名称的*非常*昂贵的方法。在某些体系结构上,获取堆栈跟踪可能会花费*数十万* 条指令,并且反射可能会导致磁盘 I/O 以获取元数据。如果代码保证使用结果调用 printf ,那么成本就不会那么高,但并非所有日志记录代码都自然而然地具有这种保证。并为适当使用 `inline` 点赞!:) (2认同)

Car*_*Dev 5

如果您不需要实际的名称空间/模块名称,或者愿意用文件名替换它,则可以使用一些特殊的属性来指示编译器为函数提供相应的参数:

open System.Runtime.CompilerServices

type Logger =
    static member Trace(
                        msg: string,
                        [<CallerMemberName>] ?memberName: string,
                        [<CallerFilePath>] ?path: string,
                        [<CallerLineNumberAttribute>] ?line: int) =
        printfn "%s from %s.%s on line %i" msg path.Value memberName.Value line.Value

module Foo =
    let bar() =
        Logger.Trace("hello")

// hello from c:\path\to\Logging.fsx.bar on line 32
Foo.bar()
Run Code Online (Sandbox Code Playgroud)

MS Docs对于“ 来电者信息”还有更多话要说。

请注意,当您从fsi内部运行时,行号仅在第一次时才是准确的。