2010-01-29 63 views
24

我刚刚开始在单声道中使用F#进行讨论,并且出现以下问题,我不太明白。查询printfnTextWriterFormat的信息也没有带来启发,所以我想我会在这里问。F#中的printfn类型,静态与动态字符串

在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"的类型是string以及v是。那么为什么会出现类型问题?是printfn特别的东西?据我了解,编译器已经对第一个字符串的参数进行了类型检查,例如printfn "%s" 1失败..这当然不适用于动态字符串,但我认为这只是编译器方便静态的情况。

回答

25

好问题。如果您查看printfn的类型Printf.TextWriterFormat<'a> -> 'a,您会发现编译器会在编译时自动将字符串强制转换为TextWriterFormat对象,从而推断出适当的类型参数'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,而不是调用构造函数:

let (s:Printf.TextWriterFormat<_>) = "hello" 
let (s':Printf.TextWriterFormat<_>) = "Here's an integer: %i" 
let (s'':Printf.TextWriterFormat<_>) = "Float: %f; Bool: %b" 

如果字符串是真正的动态(例如,它从文件中读取),那么你就需要明确地使用类型参数和调用构造函数,像我一样在前面的例子中。

7

我认为,在printfn "hello"的上下文中使用时,字面值“hello”的类型为String并不正确。在这种情况下,编译器推断文字值的类型为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 ;; 
4

正如你正确地观察,在printfn功能采用 “Printf.TextWriterFormat <“一>” 不是一个字符串。编译器知道如何在常量字符串和“Printf.TextWriterFormat <'a>”之间进行转换,但不能在动态字符串和“Printf.TextWriterFormat <'a>”之间进行转换。

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

为了让您在示例“printfn”%s“”中清楚一点,“%s”被转换为“Printf.TextWriterFormat unit>”,意思是“printfn”%s“”的类型是字符串 - >单位。

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#越多,我发现对于部分功能应用的用途就越多。好东西。