OCaml Printf.sprintf
Почему это происходит?
# Printf.sprintf ("Foo %d %s") 2 "bar";;
- : string = "Foo 2 bar"
# Printf.sprintf ("Foo %d" ^ " %s") 2 "bar";;
Printf.sprintf ("Foo %d" ^ " %s") 2 "bar";;
Error: This expression has type string but an expression was expected of type
('a -> 'b -> 'c, unit, string) format =
('a -> 'b -> 'c, unit, string, string, string, string) format6
Я бы ожидал, что сначала будет оценена конкатенация строк, так что все будет нормально. Имеет ли это отношение к обману системы типов, который использует Printf?
Ответы
Ответ 1
Да, это связано с трюком системы. Если вы хотите создать строку формата, вам нужно использовать оператор (^^):
# Printf.sprintf ("Foo %d" ^^ " %s") 2 "bar";;
- : string = "Foo 2 bar"
Я не очень учусь в этом обмане, но я считаю, что компилятор готов продвигать строчную константу в формате printf, если для этого требует контекст ввода. Однако результат ("Foo %d" ^ " %s")
не является строковой константой, поэтому он не продвигается. Оператор (^^) создает контекст ввода, в котором оба операнда могут быть продвинуты, если они являются строковыми константами.
Вы можете понять, почему она должна быть строковой константой: иначе связанные типы (печатаемых значений не могут быть определены).
Ответ 2
Проблема встречается гораздо шире, чем просто с оператором ^
. В принципе, компилятор OCaml должен знать, что ваша строка формата является литеральной строкой, а буквальная строка должна быть известна во время компиляции. Кроме того, OCaml не может использовать вашу строку во время компиляции для этого типа BLAHBLAH format6
. Модуль Printf
работает корректно только со строками формата, которые полностью известны во время компиляции, или с строками формата, которые уже переданы в тип BLAHBLAH format
.
Как правило, эту проблему можно решить с помощью оператора ^^
и путем явного литья всех литеральных строк в тип BLAHBLAH format
, прежде чем использовать эти строки в коде.
Вот еще один пример:
# Printf.sprintf (if true then "%d" else "%d ") 2;;
Error: This expression has type string but an expression was expected of type
('a -> 'b, unit, string) format =
('a -> 'b, unit, string, string, string, string) format6
(* define a type abbreviation for brevity *)
# type ('a,'b) fformat = ('a ->'b, unit, string) format;;
type ('a, 'b) fformat = ('a -> 'b, unit, string) format
# Printf.sprintf (if true then ("%d":('a,'b)fformat) else ("%d ":('a,'b)fformat)) 2;;
- : string = "2"
Система OCaml не может распознать, что if ... then "a" else "b"
может быть переведена на BLAHBLAH format
. Если вы набросаете каждую литеральную строку на BLAHBLAH format
, тогда все будет работать. (Примечание: это не сработает, если вы попытаетесь передать весь if/then/else
в BLAHBLAH format
, так как OCaml не может проверить, что ваша строка является литералом.)
Источником проблемы является требование безопасности типа: OCaml требует наличия аргумента правильного типа для всех %d
и %s
и т.д. и гарантирует это во время компиляции. Вы не можете гарантировать безопасность типа с помощью Printf
, если во время компиляции не известна вся строка формата. Поэтому невозможно использовать Printf
со строкой формата, вычисленной с помощью сложного алгоритма, например, путем выбора случайных значений %s
и %d
.
Когда мы используем if/then/else
для вычисления строки формата, то OCaml-вещи, о, это сложный алгоритм, и безнадежно проверять безопасность типа во время компиляции. Оператор ^^
знает о типах BLAHBLAH format
и дает правильный результат при конкатенации строк формата. Но if/then/else
не знает о BLAHBLAH format
, и нет встроенной альтернативы if/then/else
(но я думаю, вы могли бы определить такую вещь самостоятельно).