如何使用printf样式记录参数创建F#函数?

时间:2011-04-06 16:37:54

标签: f# printf

我正在尝试创建一个框架来处理文件和数据。我正在努力解决的一个领域是如何为框架提供日志记录功能,允许框架在不知道正在使用的日志记录的情况下报告消息。

let testLogger (source:seq<'a>) logger =
    logger "Testing..."
    let length = source |> Seq.length
    logger "Got a length of %d" length


let logger format = Printf.kprintf (printfn "%A: %s" System.DateTime.Now) format
testLogger [1; 2; 3] logger

理想情况下,我希望此代码可以正常工作,但我无法确定如何传递记录器功能。

2 个答案:

答案 0 :(得分:14)

正如Tomas所指出的,F#中的函数不需要多态参数。在这种情况下,我认为Tomas的方法非常好,因为您可能只需要能够传递用于记录的string -> unit函数。

但是,如果你真的想要传递相当于多态函数,一种解决方法是使用单个泛型方法创建一个简单类型,并传递该类型的实例:

type ILogger = abstract Log : Printf.StringFormat<'a,unit> -> 'a

let testLogger (source:seq<'a>) (logger:ILogger) = 
    logger.Log "Testing..."
    let length = source |> Seq.length        
    logger.Log "Got a length of %d" length

let logger = { 
    new ILogger with member __.Log format = 
        Printf.kprintf (printfn "%A: %s" System.DateTime.Now) format }

为了使用类型推断更好地工作,你可以用一个简单的辅助函数定义一个模块:

module Log =
    let logWith (logger : ILogger) = logger.Log

let testLogger2 (source:seq<'a>) logger =
    Log.logWith logger "Testing..."
    let length = source |> Seq.length        
    Log.logWith logger "Got a length of %d" length

这个最终结果看起来很像Tomas的解决方案,但是在定义记录器方面给你的灵活性更高一些,在这种情况下,这对你来说可能有用,也可能没有用。

答案 1 :(得分:11)

不幸的是,您无法将printf之类的函数作为参数传递给其他函数,然后使用使用多个不同的参数。问题是printf是类型Printf.TextWriterFormat<'a> -> 'a的通用函数。替换类型参数'a的实际类型是某些函数类型,每次使用printf时都会有所不同(例如'a == string -> unit"%s"等。)

在F#中,您不能拥有本身就是通用函数的函数的参数。泛型函数必须是一些全局函数,但您可以通过实际对字符串执行某些操作的函数对其进行参数化。这基本上是kprintf的作用,但您可以更好地命名您的函数:

let logPrintf logger format = 
    Printf.kprintf logger format

记录器参数化的函数示例如下:

let testLogger (source:seq<'a>) logger =
    logPrintf logger "Testing..."
    let length = source |> Seq.length
    logPrintf logger "Got a length of %d" length


let logger = printfn "%A: %s" System.DateTime.Now
testLogger [1; 2; 3] logger