【问题标题】:Why does F#'s printfn work with literal strings, but not values of type string?为什么 F# 的 printfn 可以处理文字字符串,但不能处理字符串类型的值?
【发布时间】:2021-11-16 20:33:51
【问题描述】:

在以下 F# 代码中;我希望printfn 被调用了三次;每个都有一个字符串。但是,底线无法编译(The type 'string' is not compatible with the type 'Printf.TextWriterFormat<'a>')。

前两行意味着这可以工作吗?它们不也只是字符串吗?

open System

printfn ("\r\n") // Works
printfn ("DANNY") // Works
printfn (DateTime.Now.ToLongTimeString()) // Doesn't compile

【问题讨论】:

  • 我不明白为什么,既然有printfprint format,为什么不是一个print,它只是在没有格式化的情况下打印?将其定义为 let print (x : string) = printf "%s" x 很容易,但这似乎很愚蠢,而且可能效率低下。
  • 有一个被批准的建议添加print 作为 Console.WriteLine 的别名。 github.com/fsharp/fslang-suggestions/issues/1092

标签: .net f#


【解决方案1】:

F# 编译器静态分析您传递给printfn 的格式字符串,以检查您传递的参数对于您使用的格式说明符是否有效。例如,以下内容无法编译:

printfn "%d" "some value"

因为 string 与 %d 格式说明符不兼容。编译器将有效格式字符串转换为TextWriterFormat<T>

它不能对任意字符串执行此操作,并且由于它不进行转换,因此会出现上述类型错误。

您可以使用Printf.TextWriterFormat 自己进行转换。 例如,对于需要 stringint 的格式字符串,您可以使用:

let f = Printf.TextWriterFormat<string -> int -> unit>("The length of '%s' is: %d")
printfn f "something" 9

由于您的字符串没有格式占位符,您可以这样做:

let f = Printf.TextWriterFormat<unit>(DateTime.Now.ToLongTimeString())
printfn f

【讨论】:

  • 啊;我忘记了编译器在这里变魔术了;我在考虑运行时。完全有道理,这只适用于编译时可用的字符串!
  • 从字符串到 TextWriterFormat 的隐式转换是 F# 专门为 printf 做的吗,或者你也可以用你自己的类型来做这个吗?
  • “这完全适用于编译时可用的字符串!” ——嗯,不。 let printsd fmt = Printf.TextWriterFormat&lt;string -&gt; int -&gt; unit&gt; fmt |&gt; printfn 有效。如果 fmt 没有正确的格式说明符,它会在运行时失败。
【解决方案2】:

根据可能的解决方法,@Lee 的回答是正确的,但它没有描述您的代码会发生什么。

在表达式printf "foo" 中,"foo" 不是要格式化的字符串。相反,它是自己的输入格式化程序。更具体地说,它是一个字符串字面量,用于推断TextWriterFormat&lt;'T&gt; 的实际类型。

printf的签名是:

printf : TextWriterFormat<'T> -> 'T

由于printfn ("DANNY") 不包含任何格式说明符,F# 编译器推断出TextWriterFormat&lt;unit&gt;,整个表达式变为printfn ("DANNY") ()

使用变量,不可能静态地预测其中的格式说明符。考虑如果ToLongTimeString() 方法能够返回"%s""%d %d %d" 的字符串,返回函数的原型是什么?

当推断出正确的类型时,字符串文字可以正常工作,但变量或let-binding 不起作用:

let foo1 = "foo"
let bar = printf foo // does not compile
[<Literal>] let foo2 = "foo";; // see update below
let bar = printf foo2 // compiles fine

无论如何,总是使用格式说明符看起来更安全:

printf "%s" "DANNY"
printf "%s" (DateTime.Now.ToLongTimeString())

更新:不要忘记在[&lt;Literal&gt;] 值之后输入双冒号;; 以避免在VS2013 中警告FS0058

【讨论】:

  • 在 let main argv = ... 中添加文字会给出意外符号 [
猜你喜欢
  • 1970-01-01
  • 2018-08-30
  • 2021-12-23
  • 1970-01-01
  • 1970-01-01
  • 2021-11-30
  • 1970-01-01
  • 2020-09-25
  • 2019-05-03
相关资源
最近更新 更多