I\'d like to make a function in F# that accepts a printf-style function as an argument, and uses that argument to output data. Usage would be something like the following:
I think the answer by kvb is a great explanation of the problem - why is it difficult to pass printf
like functions to other functions as parameter. While kvb gives a workaround that makes this possible, I think it is probably not very practical (because the use of interfaces makes it a bit complex).
So, if you want to parameterize your output, I think it is easier to take System.IO.TextWriter
as an argument and then use printf
like function that prints the output to the specified TextWriter
:
let OutputStuff printer =
Printf.fprintfn printer "Hi there!"
Printf.fprintfn printer "The answer is: %d" 42
OutputStuff System.Console.Out
This way, you can still print to different outputs using the printf
style formatting strings, but the code looks a lot simpler (alternatively, you could use Printf.kprintf
and specify a printing function that takes string
instead of using TextWriter
).
If you want to print to an in-memory string, that's easy too:
let sb = System.Text.StringBuilder()
OutputStuff (new System.IO.StringWriter(sb))
sb.ToString()
In general, TextWriter
is a standard .NET abstraction for specifying printing output, so it is probably a good choice.
The problem is that when you say (output : Printf.TextWriterFormat<'a> -> 'a)
, that means "there is some 'a
for which output takes a Printf.TextWriterFormat<'a>
to an 'a
". Instead, what you want to say is "for all 'a
output can take a Printf.TextWriterFormat<'a>
and return a 'a
.
This is a bit ugly to express in F#, but the way to do it is with a type with a generic method:
type IPrinter =
abstract Print : Printf.TextWriterFormat<'a> -> 'a
let OutputStuff (output : IPrinter) =
output.Print "Header"
output.Print "Data: %d" 42
OutputStuff { new IPrinter with member this.Print(s) = printfn s }
Using the inline
feature of FSharp you can pre-configure the Printf.ksprintf
function with a "work" function and thereby have a final function that accepts a format string together with its peculiar required parameters similar to printf
or sprintf
. The work function, which accepts that resulting string
, can presumably do whatever it wants such as in the case of this logging example which prints the string
to the console
:
let logger = fun (msg:string) -> System.Console.WriteLine msg
let inline log msg = Printf.ksprintf logger msg
Usage examples:
open System
open System.Globalization.CultureInfo.CurrentCulture
log "Hello %s, how is your %s" name Calendar.GetDayOfWeek(DateTime.Today)
log "132 + 6451 = %d" (132+6451)
...