Почему F # printfn работает с литеральными строками, но не значениями строки типа?

В следующем коде F #; Я ожидал бы, что printfn вызывается три раза; каждая со строкой. Однако нижняя строка не компилируется (The type 'string' is not compatible with the type 'Printf.TextWriterFormat<'a>').

Что это за первые две строки, которые означают, что это может работать? Разве это не просто строки?

open System

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

Ответы

Ответ 1

Компилятор F # статически анализирует строки формата, которые вы передаете в printfn, чтобы проверить, что аргументы, которые вы передаете, действительны для используемых вами спецификаторов формата. Например, следующее не компилируется:

printfn "%d" "some value"

поскольку string несовместим с спецификатором формата% d. Компилятор преобразует строки корректного формата в TextWriterFormat<T>.

Он не может сделать это с произвольными строками, и поскольку он не выполняет преобразование, вы получаете ошибку типа выше.

Вы можете сделать преобразование самостоятельно, используя Printf.TextWriterFormat. Например, для строки формата, требующей string и int, вы можете использовать:

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

Поскольку ваша строка не имеет заполнителей формата, вы можете сделать:

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

Ответ 2

@Ответ на вопрос верен в соответствии с возможным обходным путем, но он не описывает, что происходит с вашим кодом.

В выражении printf "foo" "foo" не является строкой, которая должна быть отформатирована. Вместо этого это входной форматтер. Более конкретно, это строковый литерал, используемый для вывода фактического типа TextWriterFormat<'T>.

Подпись printf:

printf : TextWriterFormat<'T> -> 'T

Так как printfn ("DANNY") не содержит спецификаторов формата, компилятор F # передает a TextWriterFormat<unit>, и все выражение становится printfn ("DANNY") ().

С переменной невозможно статически предсказать, какие спецификаторы формата будут там. Если метод ToLongTimeString() смог вернуть строки "%s" или "%d %d %d", каков был бы прототип возвращенной функции?

При выводе правильного типа строковый литерал работает отлично, но переменная или let -связывание не работает:

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

В любом случае, гораздо безопаснее всегда использовать спецификаторы формата:

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

Обновить: не забудьте ввести двойной двоеточие ;; после [<Literal>], чтобы избежать предупреждения FS0058 в VS2013.