How do I create an F# function with a printf style logging argument?
I'm开发者_Python百科 trying to create a framework to do some processing of files and data. The one area I'm struggling with is how to provide a logging function to the framework, allowing the framework to report messages without having any knowledge of the logging in use.
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
Ideally I want this code to work, but I can't work out how to pass the logger function in.
As Tomas points out, functions in F# can't require polymorphic arguments. In this case, I think that Tomas's approach is quite nice, since you probably only need to be able to pass around a string -> unit
function which is used for logging.
However, if you really do want to pass around the equivalent of a polymorphic function, one workaround is to create a simple type with a single generic method, and pass an instance of that type:
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 }
To make this work more nicely with type inference, you could define a module with a simple helper function:
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
This end result looks a lot like Tomas's solution, but gives you a bit more flexibility in how you define your logger, which may or may not actually be useful to you in this case.
Unfortunately, you cannot pass functions like printf
as parameters to other functions and then use them with multiple different arguments. The problem is that printf
is a generic function of type Printf.TextWriterFormat<'a> -> 'a
. The actual type substituted for the type parameter 'a
is some function type that is different each time you use printf
(e.g. 'a == string -> unit
for "%s"
etc).
In F#, you cannot have parameter of a function that is itself a generic function. The generic function will have to be some global function, but you can parameterize it by the function that actually does something with the string. This is essentially what kprintf
does, but you can name your function better:
let logPrintf logger format =
Printf.kprintf logger format
An example of function parameterized by the logger would be:
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
精彩评论