我正在尝试创建一个框架来处理文件和数据。我正在努力解决的一个领域是如何为框架提供日志记录功能,允许框架在不知道正在使用的日志记录的情况下报告消息。
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
理想情况下,我希望此代码可以正常工作,但我无法确定如何传递记录器功能。
答案 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