Тип 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" → .