Тип printfn в F #, статическая или динамическая строка
Я только начал заниматься F # в Mono, и возникла следующая проблема, которую я не могу понять. Поиск информации о 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
is. Почему возникает проблема типа? Является ли printfn
чем-то особенным? Насколько я понимаю, компилятор уже выполняет проверку типов в аргументах первой строки, так что printfn "%s" 1
терпит неудачу.. это может, конечно, не работать с динамическими строками, но я предположил, что это просто удобство из компилятора - для статического случая.
Ответы
Ответ 1
Хороший вопрос. Если вы посмотрите на тип printfn
, который равен Printf.TextWriterFormat<'a> -> 'a
, вы увидите, что компилятор автоматически привязывает строки к объектам TextWriterFormat
во время компиляции, вызывая соответствующий параметр типа 'a
. Если вы хотите использовать printfn
с динамической строкой, вы можете просто выполнить это преобразование самостоятельно:
let s = Printf.TextWriterFormat<unit>("hello")
printfn s
let s' = Printf.TextWriterFormat<int -> unit>("Here 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 an integer: %i"
let (s'':Printf.TextWriterFormat<_>) = "Float: %f; Bool: %b"
Если строка действительно динамическая (например, она читается из файла), вам нужно явно использовать параметры типа и вызвать конструктор, как это было в предыдущих примерах.
Ответ 2
Я не думаю, что правильно сказать, что буквальное значение "привет" имеет тип String
при использовании в контексте printfn "hello"
. В этом контексте компилятор отображает тип литерала как Printf.TextWriterFormat<unit>
.
Сначала мне показалось странным, что буквальное строковое значение будет иметь другой предполагаемый тип в зависимости от контекста того, где он использовался, но, конечно, мы привыкли к этому при работе с числовыми литералами, которые могут представлять целые числа, десятичные знаки, поплавки и т.д., в зависимости от того, где они появляются.
Если вы хотите объявить переменную заранее, используя ее через printfn, вы можете объявить ее с явным типом...
let v = "hello" : Printf.TextWriterFormat<unit> in printfn v
... или вы можете использовать конструктор для Printf.TextWriterFormat
, чтобы преобразовать нормальное значение String в нужный тип...
let s = "foo" ;;
let v = new Printf.TextWriterFormat<unit>(s) in printfn v ;;
Ответ 3
Это лишь немного связано с вашим вопросом, но я считаю, что это удобный трюк. В С# я часто использую строки шаблонов для использования с 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
настаивает на литеральных строках вместо значений, я изначально думал, что не могу использовать этот подход в F #, если захочу использовать printf
. Но потом я понял, что у F # есть что-то лучшее - приложение с частичной функцией.
let formatFunction = sprintf "Some %s really long %i template %i"
Это только что создало функцию, которая вводит строку и два целых числа в качестве входных данных, и возвращает строку. То есть string -> int -> int -> string
. Это даже лучше, чем постоянный шаблон String.Format, потому что это сильно типизированный метод, который позволяет мне повторно использовать шаблон, не включая его встроенный.
let foo = formatFunction "test" 3 5
Чем больше я использую F #, тем больше я использую для приложения частичной функции. Отличный материал.
Ответ 4
Как вы правильно заметили, функция printfn принимает "Printf.TextWriterFormat <" a > "not a string. Компилятор знает, как преобразовать константную строку и "Printf.TextWriterFormat <" a > , но не между динамической строкой и "Printf.TextWriterFormat <" a > ".
Это вызывает вопрос, почему он не может преобразовать динамическую строку и "Printf.TextWriterFormat <" a > ". Это связано с тем, что компилятор должен посмотреть содержимое строки и определить, какие контрольные символы в ней (т.е.% S% я и т.д.), Из этого выработает тип параметра типа "Printf.TextWriterFormat <" a > "(т.е. "бит" ). Это функция, возвращаемая функцией printfn, и означает, что другие параметры, принятые printfn, теперь строго типизированы.
Чтобы сделать это немного ясно в вашем примере "printfn" % s "" % s "преобразуется в" Printf.TextWriterFormat unit > ", что означает, что тип "printfn" % s " "string" → .