Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create an F# function with a printf style logging argument?

Tags:

printf

f#

I'm 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.

like image 496
Nick Randell Avatar asked Apr 06 '11 16:04

Nick Randell


People also ask

How do you type an F-string?

Strings in Python are usually enclosed within double quotes ( "" ) or single quotes ( '' ). To create f-strings, you only need to add an f or an F before the opening quotes of your string. For example, "This" is a string whereas f"This" is an f-String.

What is f {} in Python?

“F-strings provide a way to embed expressions inside string literals, using a minimal syntax. It should be noted that an f-string is really an expression evaluated at run time, not a constant value. In Python source code, an f-string is a literal string, prefixed with f , which contains expressions inside braces.


1 Answers

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.

like image 183
kvb Avatar answered Sep 28 '22 02:09

kvb