2010-01-29 61 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#越多,我發現對於部分功能應用的用途就越多。好東西。