3

我想在 F# 中创建一个函数,它接受 printf 样式的函数作为参数,并使用该参数输出数据。用法如下所示:

OutputStuff printfn

我的第一次尝试是让编译器为我解决所有问题:

let OutputStuff output =
    output "Header"
    output "Data: %d" 42

那失败了,因为它决定那output是一个函数 takestring和 return unit,所以第二次调用失败了。

接下来,我尝试声明output具有与以下相同的签名printfn

let OutputStuff (output : Printf.TextWriterFormat<'a> -> 'a) =
    output "Header"
    output "Data: %d" 42

这失败了,因为编译器决定真正的类型outputPrintf.TextWriterFormat<string> -> unit,所以第二次调用再次失败。它还会生成警告 FS0064,表明第一次调用output导致代码比类型注释更通用,这是问题的症结所在。

最后,我尝试将输出函数声明为单独的类型缩写:

type OutputMe<'a> = Printf.TextWriterFormat<'a> -> 'a
let OutputStuff (output : OutputMe<'a>) =
    output "Header"
    output "Data: %d" 42

这失败了,结果与上一次尝试相同。

如何说服编译器不专门化 of 的类型output并将其保留为Printf.TextWriterFormat<'a> -> 'a

4

3 回答 3

7

问题是,当您说时(output : Printf.TextWriterFormat<'a> -> 'a),这意味着“有些'a输出需要 a到Printf.TextWriterFormat<'a>a 'a”。相反,您想说的是“对于所有 'a输出都可以采用 aPrintf.TextWriterFormat<'a>并返回 a 'a

这在 F# 中表达起来有点难看,但方法是使用具有泛型方法的类型:

type IPrinter =
    abstract Print : Printf.TextWriterFormat<'a> -> 'a

let OutputStuff (output : IPrinter) =
    output.Print "Header"
    output.Print "Data: %d" 42

OutputStuff { new IPrinter with member this.Print(s) = printfn s }
于 2013-04-08T17:21:01.760 回答
5

我认为 kvb 的回答很好地解释了这个问题——为什么很难将printf类似的函数作为参数传递给其他函数。虽然 kvb 提供了一个使这成为可能的解决方法,但我认为它可能不是很实用(因为接口的使用使它有点复杂)。

所以,如果你想参数化你的输出,我认为它更容易System.IO.TextWriter作为一个参数,然后使用printf类似的函数将输出打印到指定的TextWriter

let OutputStuff printer =
  Printf.fprintfn printer "Hi there!"
  Printf.fprintfn printer "The answer is: %d" 42

OutputStuff System.Console.Out

这样,您仍然可以使用printf样式格式化字符串打印到不同的输出,但代码看起来要简单得多(或者,您可以使用Printf.kprintf并指定一个打印函数,string而不是 using TextWriter)。

如果要打印到内存中的字符串,这也很容易:

let sb = System.Text.StringBuilder()
OutputStuff (new System.IO.StringWriter(sb))
sb.ToString()

通常,TextWriter它是用于指定打印输出的标准 .NET 抽象,因此它可能是一个不错的选择。

于 2013-04-08T17:26:21.147 回答
1

使用inlineFSharp 的功能,您可以使用“工作”函数预先配置该Printf.ksprintf函数,从而获得一个最终函数,该函数接受格式字符串及其类似于printfor的特殊必需参数sprintf。接受结果的工作函数string可能可以做任何它想做的事情,例如在这个记录示例的情况下,它打印stringconsole

let logger = fun (msg:string) -> System.Console.WriteLine msg
let inline log msg = Printf.ksprintf logger msg

使用示例:

open System
open System.Globalization.CultureInfo.CurrentCulture

log "Hello %s, how is your %s" name Calendar.GetDayOfWeek(DateTime.Today)

log "132 + 6451 = %d" (132+6451)

...
于 2017-11-17T04:29:49.797 回答