TL;DR: how to write a function like fprintfn
that accesses and closes a file and behaves similar to printf
family of functions, but won't throw an ObjectDisposedException
on multiple arguments?
As a convenience embedded function, I found myself writing the following code, which worked for a while:
let fprintfn a b =
use handle = File.AppendText("somelogfile.log")
printfn a b |> ignore // write to stdout
fprintfn handle a b // write to logfile
// works
fprintfn "Here we are, %i years of age" 42
// throws ObjectDisposedException
fprintfn "Here we are, %i years of age in $i" 42 DateTime.Now.Year
The error is raised whether I use use
or using
and as soon as the number of arguments exceeds 2.
After some head-scratching, I concluded that the new fprintfn
function above creates a closure as soon as I use it with more than one print format argument. Which seems to make sense to some extend, albeit a bit more hidden then in cases where you return a closure explicitly (i.e. returning an actual fun x -> something
which accesses the handle
variable).
Now the question is: how can I rewrite the above statement while retaining the convenience of the original fprintfn
function use and syntax, without having it throw the ObjectDisposedException
?
PS: A preferred way of writing the above function would be to use the following, which allows all fprintfn
syntax, but that will throw the same exception already when you use a single print format argument.
let fprintfn a =
use handle = File.AppendText("somelogfile.log")
printfn a |> ignore
fprintfn handle a
fprintf "Test" // works
fprintf "Test: %i" 42 // throws ODE
You are right that fprintf
creates a continuation and returns it, so that by the time you call that continuation, the file is already closed.
But you can go one level deeper and use kprintf
. It lets you provide a "continuation" - i.e. a function that receives the formatted string and does whatever with it.
let fprintfn a =
let doPrint s =
use handle = File.AppendText("somelogfile.log")
printfn "%s" s
fprintfn handle "%s" s
kprintf doPrint a
And then, of course, you can simplify that a bit by using File.AppendAllText
:
let fprintfn a =
let doPrint s =
File.AppendAllText("somelogfile.log", s + "\n")
printfn "%s" s
kprintf doPrint a