В настоящее время я пытаюсь изучить OCaml с помощью учебника «Real World OCaml: функциональное программирование для масс». Я дошел до того, что попытался создать свой первый исполняемый файл после игры внутри utop, но программа продолжает зависать следующим образом. Сам код точно такой же, как в книге:
open Base
open Stdio
let rec read_and_accumulate accum =
let line = In_channel.input_line In_channel.stdin in
match line with
| None -> accum
| Some x -> read_and_accumulate (accum +. Float.of_string x)
let () =
printf "Total: %F\n" (read_and_accumulate 0.)
И лог ошибок после попытки сборки с дюны и запуска выдает
Fatal error: exception Invalid_argument("Float.of_string ")
Raised at Stdlib.invalid_arg in file "stdlib.ml", line 30, characters 20-45
Called from Dune__exe__Main.read_and_accumulate in file "bin/main.ml", line 8, characters 44-61
Called from Dune__exe__Main in file "bin/main.ml", line 11, characters 23-47
Признаюсь, я не совсем понимаю, почему Float.of_string
кажется здесь причиной проблемы, поскольку внутри utop он работает нормально, когда я его тестирую.
Я должен добавить, что я использую OCaml версии 4.13.1 в Debian и что установлены как Base, так и Stdio. Я также могу добавить скриншот ниже для пояснения:
@mkrieger1 К сожалению, изменение строки на Some x -> read_and_accumulate (accum +. (Float.of_string x))
не меняет результат. Запуск dune build
, dune exec sum
и набор некоторых чисел приводит к тому же журналу ошибок.
Вероятно, это означает, что все, что вы читаете из командной строки, недопустимо для Float.of_string
.
Пожалуйста, опубликуйте образец в виде текста. Единственное вероятное объяснение заключается в том, что вы предоставляете что-то, что нельзя преобразовать в float
. Учитывая то, что вы нам показали, я могу только предположить, что вы предоставляете пустую строку, а не просто EOF.
Вероятно, вам придется обрезать строку, чтобы удалить символ новой строки, который предотвращает анализ строки как числа с плавающей запятой.
Вы изучаете OCaml или учитесь программировать? Предложите вам поработать над тем, чтобы ваш код эффективно обрабатывал такие вещи, как исключения из Float.of_string
, а не позволял им завершать выполнение. Единственными исключениями, с которыми это следует делать, являются вещи, с которыми вы действительно не можете справиться.
Как упоминалось в комментариях, правильный способ завершить программу — нажать Ctrl-D
вместо того, чтобы дважды нажать Enter
, моя вина.
Как вы заметили, проблема связана с предоставленными вами данными. Вместо подачи сигнала EOF (конец файла) вы ввели пустую строку. Мы можем легко увидеть, что это вызывает исключение.
# In_channel.(input_line stdin);;
- : string option = Some ""
# Float.of_string "";;
Exception: Failure "float_of_string".
Основное решение очень простое: нажмите Ctrl-D, чтобы отправить сигнал EOF. Однако мы можем рассмотреть альтернативные способы обработки этого исключения, которые могут быть поучительны.
Примечание: далее используются только стандартные библиотечные модули и функции.
Вы можете обработать исключение и использовать его для прекращения рекурсии.
let rec read_and_accumulate accum =
let line = In_channel.input_line In_channel.stdin in
match line with
| None -> accum
| Some x ->
(try
read_and_accumulate (accum +. Float.of_string x)
with
| Failure _ -> accum)
Вы также можете пропустить этот «неправильный» ввод.
let rec read_and_accumulate accum =
let line = In_channel.input_line In_channel.stdin in
match line with
| None -> accum
| Some x ->
(try
read_and_accumulate (accum +. Float.of_string x)
with
| Failure _ -> read_and_accumulate accum)
Вы также можете извлечь процесс чтения входных данных и получения суммы значений с плавающей запятой:
let rec read_lines () =
match In_channel.(input_line stdin) with
| None -> []
| Some line -> line :: read_lines ()
Эта функция будет читать список строк, который мы затем можем обработать с помощью функций List модуля.
# read_lines ()
|> List.filter_map
(fun line ->
try Some (Float.of_string line)
with Failure _ -> None)
|> List.fold_left (+.) 0.;;
5.0
7.1
8.42
7.0
2.0
- : float = 31.52
Вы можете справедливо сказать, что создание списка не является строго обязательным, и вы будете правы, поэтому OCaml предоставляет модуль для ленивых последовательностей: Seq.
# let rec read_lines_seq () =
match In_channel.(input_line stdin) with
| None -> Seq.Nil
| Some line -> Seq.Cons (line, read_lines_seq);;
val read_lines_seq : string Seq.t = <fun>
# read_lines_seq
|> Seq.filter_map
(fun line ->
try Some (Float.of_string line)
with Failure _ -> None)
|> Seq.fold_left (+.) 0.
;;
6
7
8
1.2
3.4
- : float = 25.5999999999999979
Спасибо за этот подробный ответ. Я только недавно начал заниматься реальным программированием (только занимаясь R для классов статистики и немного Python) с математическим образованием, а не с CS. В основном я делаю это ради удовольствия от обучения, немного изучаю C, немного OCaml, немного Ada.
Что произойдет, если вы используете
(Float.of_string x)
вместоFloat.of_string x
?