我刚刚开始在Mono中使用F#,并且出现了以下问题,我无法理解。查找关于printfn
和TextWriterFormat
的信息也没有带来启示,所以我想我会在这里问。
在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
有什么特别之处吗?据我所知,编译器已对第一个字符串的参数执行类型检查,这样{{1}}失败..这当然不适用于动态字符串,但我认为这只是一个方便的静态情况下的编译器端。
答案 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;单元。