I was wondering if there is a nice way to define a function captureOutput
which takes a function f
that may have contain print statements and return whatever it is that f
printed. e.g.,
let f x = print "%s" x
let op = captureOutput (f "Hello World")
val op : string = "Hello World"
I was thinking that perhaps there is a nice way to do this asynchronously with Console.ReadLine()
but I haven't been able to work anything out.
Cheers
EDIT:
Based on the comment of Fyodor Soikin, the following code does what I want:
let captureOutput f x =
let newOut = new IO.StringWriter()
Console.SetOut(newOut)
f x
Console.SetOut(Console.Out)
newOut.ToString()
You can temporarily replace the standard output writer via Console.SetOut
.
But beware: this replacement will also affect code executing on other threads and capture their output as well, intermixed with your function's output. This is essentially what is known as a "hack".
If this is only for a very small utility that will never become more complex, then this is fine. But this should never become part of a production system. If you're developing a part of something complex, I recommend changing the function itself, parameterizing it over the printing function:
type Printer = abstract member print (fmt: StringFormat<'T, unit>) : 'T
let captureOutput f =
let mutable output = ""
let print s = output <- output + s
f { new Printer with member _.print fmt = kprintf print fmt }
output
let f x (p: Printer) = p.print "%s" x
let op = captureOutput (f "Hello World")
(this example has to use an interface, because without it the print
function would lose genericity)
Implementing @FyodorSoikin's suggestion (which I only saw after I had this written out):
let captureOutput f =
use writer = new StringWriter()
use restoreOut =
let origOut = Console.Out
{ new IDisposable with member __.Dispose() = Console.SetOut origOut }
Console.SetOut writer
f ()
writer.ToString ()
let f x () = printf "%s" x
let op = captureOutput (f "Hello World")
(N.b. an extra argument had to be added to f
so that it could be partially applied – captureOutput
must take a function, not a value).
An object-expression for IDisposable
is used to wrap the cleanup so that calls to f
are exception-safe.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With