Почему 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.