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 (но я думаю, вы могли бы определить такую ​​вещь самостоятельно).