ira
ira

Reputation: 745

Capturing print statement in f#

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()

Upvotes: 5

Views: 322

Answers (2)

Fyodor Soikin
Fyodor Soikin

Reputation: 80744

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)

Upvotes: 5

ildjarn
ildjarn

Reputation: 62975

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.

Upvotes: 3

Related Questions