F#中的printfn类型,静态与动态字符串

时间:2010-01-29 13:09:22

标签: f#

我刚刚开始在Mono中使用F#,并且出现了以下问题,我无法理解。查找关于printfnTextWriterFormat的信息也没有带来启示,所以我想我会在这里问。

在FSI中,我运行以下内容:

> "hello";;
val it : string = "hello"
> printfn "hello";;
hello
val it : unit = ()

只是一个普通的字符串并打印出来。精细。现在我想声明一个变量来包含相同的字符串并打印它:

> let v = "hello" in printfn v ;;
let v = "hello" in printfn v ;;
---------------------------^
\...\stdin(22,28): error FS0001: The type 'string' is not compatible with the type 'Printf.TextWriterFormat<'a>'

我从阅读中了解到printfn需要一个常量字符串。我也明白,我可以用printfn "%s" v之类的东西解决这个问题。

但是,我想了解这里输入的内容。显然,"hello"的类型为stringvprintfn。为什么会出现类型问题? printfn "%s" 1有什么特别之处吗?据我所知,编译器已对第一个字符串的参数执行类型检查,这样{{1}}失败..这当然不适用于动态字符串,但我认为这只是一个方便的静态情况下的编译器端。

4 个答案:

答案 0 :(得分:25)

好问题。如果查看printfn的类型Printf.TextWriterFormat<'a> -> 'a,您将看到编译器在编译时自动将字符串强制转换为TextWriterFormat个对象,并推断出相应的类型参数{{1 }}。如果您想将'a与动态字符串一起使用,您可以自己执行该转换:

printfn

如果字符串是静态知道的(如上例所示),那么你仍然可以让编译器向let s = Printf.TextWriterFormat<unit>("hello") printfn s let s' = Printf.TextWriterFormat<int -> unit>("Here's an integer: %i") printfn s' 10 let s'' = Printf.TextWriterFormat<float -> bool -> unit>("Float: %f; Bool: %b") printfn s'' 1.0 true 推断出正确的泛型参数,而不是调用构造函数:

TextWriterFormat

如果字符串是真正动态的(例如,它是从文件中读取的),那么您需要显式使用类型参数并像前面的示例中那样调用构造函数。

答案 1 :(得分:7)

String的上下文中使用时,我认为文字值“hello”的类型为printfn "hello"并不正确。在此上下文中,编译器将文字值的类型推断为Printf.TextWriterFormat<unit>

起初我觉得奇怪的是,文字字符串值会根据其使用位置的上下文而具有不同的推断类型,但当然在处理可能代表整数的数字文字时我们会习惯这种情况,小数,浮点数等,取决于它们出现的位置。

如果要在通过printfn使用它之前声明变量,可以使用显式类型声明它...

let v = "hello" : Printf.TextWriterFormat<unit> in printfn v

...或者您可以使用Printf.TextWriterFormat的构造函数将普通字符串值转换为必要的类型...

let s = "foo" ;;
let v = new Printf.TextWriterFormat<unit>(s) in printfn v ;;

答案 2 :(得分:7)

这只与您的问题有些相关,但我认为这是一个方便的伎俩。在C#中,我经常使用模板字符串与String.Format一起存储为常量,因为它使代码更清晰:

String.Format(SomeConstant, arg1, arg2, arg3)

而不是......

String.Format("Some {0} really long {1} and distracting template that uglifies my code {2}...", arg1, arg2, arg3)

但由于printf系列方法坚持使用文字字符串而不是值,我最初认为如果我想使用printf,我就无法在F#中使用此方法。但后来我意识到F#有更好的功能 - 部分功能应用。

let formatFunction = sprintf "Some %s really long %i template %i"

刚刚创建了一个函数,它接受一个字符串和两个整数作为输入,并返回一个字符串。也就是说,string -> int -> int -> string。它甚至比一个常量的String.Format模板更好,因为它是一种强类型的方法,可以让我重新使用模板而不包括内联。

let foo = formatFunction "test" 3 5

我使用F#越多,我发现部分功能应用的用途就越多。好东西。

答案 3 :(得分:4)

正如您所正确观察的那样,printfn函数采用“Printf.TextWriterFormat&lt;'a&gt;”不是一个字符串。编译器知道如何在常量字符串和“Printf.TextWriterFormat&lt;'&gt;”之间进行转换,但不能在动态字符串和“Printf.TextWriterFormat&lt;'&gt;”之间进行转换。

这引出了一个问题,为什么它不能在动态字符串和“Printf.TextWriterFormat&lt;'&gt;”之间进行转换。这是因为编译器必须查看字符串的内容并确定其中的控件字符(即%s%i等),由此可以得出“Printf.TextWriterFormat&lt;”类型参数的类型。 A&gt;”中(即'有点')。这是printfn函数返回的函数,意味着printfn接受的其他参数现在都是强类型的。

为了使你的例子“printfn”%s“”更清楚,“%s”被转换为“printf.TextWriterFormat unit&gt;”,意味着“printfn”%s“”的类型是字符串 - &GT;单元。