Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capturing print statement in f#

Tags:

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()
like image 866
ira Avatar asked Mar 15 '17 22:03

ira


2 Answers

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)

like image 68
Fyodor Soikin Avatar answered Nov 15 '22 09:11

Fyodor Soikin


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.

like image 23
ildjarn Avatar answered Nov 15 '22 08:11

ildjarn