2013-08-31 6 views
15

W następującym kodzie F #; Spodziewam się, że printfn jest wywoływany trzy razy; każdy z ciągiem. Jednak dolna linia nie kompiluje się (The type 'string' is not compatible with the type 'Printf.TextWriterFormat<'a>').Dlaczego printfn F # działa z literalnymi łańcuchami, ale nie z wartościami łańcucha znaków?

Co takiego jest w pierwszych dwóch linijkach, co oznacza, że ​​to może zadziałać? Czy to nie tylko ciągi?

open System 

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

Odpowiedz

18

F # kompilator statycznie analizuje ciągi formacie przechodzą do printfn aby sprawdzić, że argumenty ty przechodzą obowiązują dla specyfikatorów formatu użyć. Na przykład, następujące nie kompilacji:

printfn "%d" "some value" 

od string nie jest zgodna ze specyfikacją formatu% d. Kompilator konwertuje poprawne łańcuchy formatów na TextWriterFormat<T>.

Nie można tego zrobić przy użyciu dowolnych ciągów znaków, a ponieważ konwersja nie jest wykonywana, pojawia się błąd typu powyżej.

Możesz wykonać konwersję samodzielnie, jednak używając Printf.TextWriterFormat. Na przykład dla ciągu formatu wymagającego string oraz int można użyć:

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

Ponieważ ciąg ma zastępcze formacie, można zrobić:

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

Ah; Zapomniałem, że kompilator robił tu magię; Myślałem o runtime. Ma doskonałe wyczucie, że działa tylko z ciągami dostępnymi podczas kompilacji! –

+0

Czy niejawna konwersja z ciągu do TextWriterFormat coś F # robi specjalnie dla printf, czy możesz to zrobić również z własnymi typami? –

+0

"Ma doskonałe wyczucie, że działa tylko z ciągami dostępnymi w czasie kompilacji!" -- Więc nie. 'let printsd fmt = Printf.TextWriterFormat int -> unit> fmt |> printfn' działa. A jeśli fmt nie ma odpowiednich specyfikatorów formatu, kończy się niepowodzeniem w czasie wykonywania. –

7

@ odpowiedź Lee jest prawidłowe na możliwe obejście, ale nie opisuje, co dzieje się z Twoim kodem.

W wyrażeniu printf "foo", "foo" nie jest ciągiem do sformatowania. Zamiast tego jest to formatowanie samo przez się. Dokładniej, jest to literał łańcuchowy używany do wnioskowania o rzeczywistym typie TextWriterFormat<'T>.

Podpis printf jest:

printf : TextWriterFormat<'T> -> 'T 

Od printfn ("DANNY") nie zawiera żadnych specyfikatorów formatu, F # kompilator wywodzi się TextWriterFormat<unit>, a cała ekspresja staje printfn ("DANNY")().

Ze zmienną niemożliwe jest statyczne przewidzenie, które specyfikatory formatu będą dostępne. Zastanów się, czy metoda ToLongTimeString() mogła zwrócić ciągi znaków o numerze "%s" lub "%d %d %d", jaki byłby prototyp zwracanej funkcji?

Przy wnioskowaniu prawidłowego typu, ciąg dosłowne działa dobrze, ale zmienna lub let -binding nie działa:

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

W każdym razie, wygląda o wiele bezpieczniejsze, aby zawsze używać formatu specyfikatory:

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

Aktualizacja: nie zapomnij wpisać podwójnego dwukropka ;; po [<Literal>] wartości w celu uniknięcia ostrzeżenie FS0058 w VS2013.

Powiązane problemy