在 F# 中捕获打印语句

5

我想知道是否有一种好的方法来定义一个函数captureOutput,它接受一个可能包含打印语句的函数f,并返回f打印的任何内容。例如:

let f x = print "%s" x
let op = captureOutput (f "Hello World")

val op : string = "Hello World"

我在思考,也许有一种好方法可以使用Console.ReadLine()来异步完成这个操作,但我还没有想出来。祝好。
编辑:
基于Fyodor Soikin的评论,以下代码实现了我的需求:
let captureOutput f x =
    let newOut = new IO.StringWriter()
    Console.SetOut(newOut)
    f x
    Console.SetOut(Console.Out)
    newOut.ToString()

3
你试过Console.SetOut吗? - Fyodor Soikin
太棒了,非常感谢您提供的参考!这对我来说非常完美。 - ira
1
很高兴能够帮助。但这实际上是一种妥协解决方案,需要谨慎使用。请参考我的回答。 - Fyodor Soikin
2个回答

5
你可以通过使用Console.SetOut临时替换标准输出写入器。
但要注意:这个替换也会影响其他线程上执行的代码,并将它们的输出与你的函数输出混合在一起。这本质上是所谓的“hack”。
如果这只是一个非常小的实用程序,永远不会变得更加复杂,那么这没问题。但这永远不应该成为生产系统的一部分。如果您正在开发某个复杂部分,则建议更改函数本身,使其参数化打印函数。
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") 

这个示例必须使用接口,因为如果没有它,print函数会失去泛型。


非常好,谢谢。确实这只是一个非常小的实用程序(用于为我作为TA的班级评分学生作业),所以这个解决方案非常完美。 - ira

3

实现 @FyodorSoikin 的建议(我在写完这篇文章后才看到):

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

注意: 必须添加额外的参数到 f 中,以便它可以被部分应用 - captureOutput 必须接受一个函数而不是一个值。

使用IDisposable的对象表达式来包装清理操作,以便对 f 的调用是异常安全的。


嗨ildjarn,感谢您的回复!在看到您的回复之前,我也根据@FyodorSoikin的建议在主答案中编写了自己的解决方案,它们非常相似。 - ira
相比于 try ... finallyIDisposable 的 hack 似乎有点啰嗦 :-) - Fyodor Soikin
@FyodorSoikin:F#是我唯一使用try-finally的语言;我并不是很喜欢它,在编写代码时也很少考虑。/耸肩:-] - ildjarn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接