开发者

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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜