Рекурсивное выражение, единственным базовым случаем которого является исключение [Контекст: чтение из файлов в OCaml] - PullRequest
2 голосов
/ 17 февраля 2011

Редактировать: Не обращайте внимания на этот вопрос! См. Комментарии ниже.

Мне нужно выражение OCaml, которое передается в файл (как "in_channel"), затем читает файл построчно, выполняя некоторую обработку до конца, а затем возвращает результат обработки.

Я написал этот тест:

let rec sampler_string file string_so_far =
    try 
        let line = input_line file in
        let first_two_letters = String.sub line 0 2 in
        sampler_string file (string_so_far ^ first_two_letters)
    with End_of_file -> string_so_far;;

let a = sampler_string (open_in Sys.argv.(1)) "";;

(Здесь «выполнение некоторой обработки» - это добавление первых двух символов каждой строки к текущему счетчику, и идея состоит в том, что в конце должна быть возвращена строка, содержащая первые два символа каждой строки.)

Это не работает: OCaml считает, что "sampler_string" создает что-то типа unit, а не типа string . (Затем возникают трудности, когда я пытаюсь использовать результат в виде строки.) Я думаю, что эта проблема связана с тем, что единственный базовый случай происходит в исключительной ситуации (файл End_of_file).

Итак, конкретный вопрос и общий вопрос:

  1. Есть ли способ исправить этот код, явно указав OCaml ожидать, что результатом sampler_string должна быть строка?
  2. Существует ли какой-то стандартный, лучший синтаксис для подпрограммы, которая считывает файл строка за строкой и возвращает результат построчной обработки?

Ответы [ 3 ]

3 голосов
/ 17 февраля 2011

Как говорит Дэмиен Поллет, ваша функция sampler_string прекрасно компилируется (и работает правильно) и на моей машине, ocaml v3.12.0.Однако я отвечу на ваши вопросы:

  1. Вы можете указать типы для ваших функций / значений, используя оператор :.Например, вот ваша функция с аннотированными типами.Вы заметите, что возвращаемый тип помещается в самый конец объявления функции.

    let rec sampler_string (file : in_channel) (string_so_far : string) : string = ...
    
  2. Я не знаю, есть ли лучший способ чтения файла, строка-построчно.Это определенно является болью, когда приходится сталкиваться с окончанием файла через исключение. Вот сообщение в блоге на эту тему , хотя представленная здесь функция чтения файла в список строк. Еще одна версия списка рассылки .

Несколько придирок:

  1. Вам не нужно использовать ;; для разделения функций/ значения определений, ocamlc может выяснить это из пробела.
  2. Вы должны закрыть файловые сокеты.
  3. String.sub сгенерирует исключение, если в вашем файле есть строка длиной менее 2 символов.
2 голосов
/ 17 февраля 2011

Главной особенностью стиля является избегание рекурсивных вызовов внутри обработчика исключений.Такие вызовы , а не в хвостовой позиции, поэтому вы создадите стек с достаточно большим файлом.Вместо этого используйте этот шаблон:

let rec sampler_string file string_so_far =
  match try Some (input_line file) with End_of_file -> None with
  | Some line ->
      let first_two_letters = String.sub line 0 2 in
      sampler_string file (string_so_far ^ first_two_letters)
  | None -> string_so_far

Конечно, лучшая функциональная стратегия - абстрагировать рекурсивную схему:

let rec fold_left_lines f e inch =
  match try Some (input_line inch) with End_of_file -> None with
  | Some line -> fold_left_lines f (f e line) inch
  | None -> e

, поскольку "работа со строками файла" являетсяобычно полезная операция сама по себе (подсчет строк, подсчет слов, поиск самой длинной строки, разбор и т. д. - все это конкретные экземпляры этой схемы).Тогда ваша функция:

let sampler_string file string_so_far =
  fold_left_lines (fun string_so_far line ->
      let first_two_letters = String.sub line 0 2 in
      string_so_far ^ first_two_letters)
    string_so_far file
0 голосов
/ 18 февраля 2011

Как указывал Матиас, прежде всего важно переместить рекурсивный вызов за пределы выражения try / with, чтобы его можно было оптимизировать с помощью хвостового вызова.

Однако для этого есть полустандартное решение: используйте Батарейки в комплекте . Батареи обеспечивает абстракцию Enums концепции итерации по чему-либо. Затем его инфраструктура ввода-вывода предоставляет функцию BatIO.lines_of, которая возвращает перечисление строк файла. Таким образом, вся ваша функция может стать такой:

fold (fun s line -> s ^ String.sub line 0 2) "" (BatIO.lines_of file)

Перечисление автоматически закрывает файл, когда он исчерпан или сборщик мусора.

Код можно сделать более эффективным (избегая повторной конкатенации) с помощью буфера:

let buf = Buffer.create 2048 in
let () = iter (fun line -> Buffer.add_string buf (String.sub line 0 2))
  (BatIO.lines_of file) in
Buffer.contents buf

В основном: батареи могут сэкономить вам много времени и усилий в таком коде.

...