#ocaml
#ocaml
Вопрос:
Я хочу написать функцию, которая принимает пару строк и преобразует второй элемент в формат. Я думал, что смогу использовать эту format_of_string
функцию, но, похоже, это не работает. Это упрощенная версия:
let pp fmt (e:string * string) =
let msg = format_of_string (snd e) in Format.fprintf fmt (" - "^^msg^^"@.");;
^^^^^^^
Error: This expression has type string but an expression was expected of type
('a, 'b, 'c, 'd, 'e, 'f) format6
Есть ли способ заставить мою функцию принимать пару строк?
Комментарии:
1. Вы должны прочитать отчет об ошибке по адресу caml.inria.fr/mantis/view.php?id=5591
2. Действительно! Это дает решение, которое следует использовать
Scanf.format_from_string
. Большое спасибо!
Ответ №1:
Вопреки здравому смыслу, format_of_string
не преобразует a string
в формат (это невозможно сделать так, чтобы гарантированно работало, потому что типы форматов проверяются на тип во время компиляции, но содержимое строки неизвестно во время компиляции; Scanf.format_from_string
проверяет его во время выполнения).
Скорее, ее тип val format_of_string : ('a, 'b, 'c, 'd, 'e, 'f) format6 -> ('a, 'b, 'c, 'd, 'e, 'f) format6
— он принимает формат и возвращает формат, что означает, что это, по сути, функция идентификации форматов.
Какая польза от такой функции? Необычно для выражений в OCaml, строковые литералы в OCaml «перегружены». Они могут быть либо строкового типа, либо типа format . В большинстве случаев она выводится как строковый тип. Но если вы используете ее в контексте, который ожидает тип формата, например, аргумент to Printf.printf
, компилятор выводит его как тип формата. Но что, если вы используете ее для инициализации переменной? Компилятор «по умолчанию» использует строковый тип. Однако могут быть случаи, когда вы хотите инициализировать переменную типа format . Вы не можете легко использовать аннотацию типа, потому что форматы имеют действительно сложные типы, и вы не хотите указывать это явно. Вместо этого вы можете передать строковый литерал через format_of_string
, который «заставляет» компилятор выводить его как тип формата, но в противном случае возвращает значение без изменений.
PS возможно ли, чтобы вы использовали формат вместо строки в параметре?
Комментарии:
1. Да, моя функция могла принять формат, и это было моим первым решением, но использование Scanf.format_from_string является лучшим, поскольку в моем случае он намного чище (см. Мой Ответ).
Ответ №2:
Я отвечаю на свой собственный вопрос, но я нашел решение благодаря @Pascal Cuoq в комментариях выше.
Проблема в том, что компилятор может преобразовать строку в формат, только если он способен проанализировать ее для вычисления типа формата. Вот почему format_of_string
работает только с известными строками. Итак, первым решением вышеуказанной проблемы было бы преобразовать строку перед вызовом функции, когда она известна, но это не реальный ответ на вопрос.
Лучшим решением является использование Scanf.format_from_string
:
val format_from_string : string ->
('a, 'b, 'c, 'd, 'e, 'f) format6 -> ('a, 'b, 'c, 'd, 'e, 'f) format6
где второй аргумент имеет тот же тип, что и предполагаемый формат. Так, например, в приведенном выше примере, поскольку строка не должна содержать никаких %
аргументов, она будет :
let pp fmt (e:string * string) =
let msg = Scanf.format_from_string (snd e) "" in
Format.fprintf fmt (" - "^^msg^^"@.");;
Тогда вызов этой функции корректен только для строки без %
аргумента:
# Format.printf "%a" pp ("", "abc");;
- abc
- : unit = ()
Но в противном случае возникает исключение:
# Format.printf "%a" pp ("", "abc%d");;
Exception:
Scanf.Scan_failure "format read 'abc%d' does not match specification ''".
Ответ №3:
Вы не можете принять формат в качестве аргумента и связать его с другими битами строк и использовать это как формат: система типов не позволяет выразить это.
Просто подумайте о случае, когда " - "
это было бы "%"
. Это другой пример, но он имеет тот же тип, поэтому он должен быть принят или отклонен с помощью тех же механизмов.
Делает ли строка ниже то, что вы изначально предполагали?
let pp fmt f x = Format.printf fmt " - %a@." f x ;;
Комментарии:
1. Я не уверен, потому что я хочу просто иметь возможность использовать
@.
and@
в моей строке. Но работает, если я вызываюformat_of_string
свою исходную строку перед вызовом функции. Он менее чистый, но работает. В любом случае спасибо.