I'm currently trying to learn OCaml via the "Real World OCaml: Functional Programming for the Masses" textbook. I've reached the point of trying to build my first executable after playing inside utop, but the program keeps crashing in the following manner. The code itself is the exact same from the book:
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.)
And the error log after trying to build it with dune and running gives
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
I'll admit that I'm not too sure why Float.of_string
seems to be the cause of the problem here, as it works just fine inside utop when I test it.
I should add that I'm running OCaml version 4.13.1 on Debian and that both Base and Stdio are installed. I can also add the screenshot below for clarification:
As you've noted, the issue is with the input you provided. Rather than providing an EOF (end-of-file) signal, you've entered a blank line. We can readily see that this causes an exception.
# In_channel.(input_line stdin);;
- : string option = Some ""
# Float.of_string "";;
Exception: Failure "float_of_string".
The basic solution is very simple: press Ctrl-D to send the EOF signal. However, we can look at alternate ways to handle this exception which may be instructive.
Note: only standard library modules and functions used in the following.
You can handle the exception and use that to terminate the recursion.
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)
You could also skip that "faulty" input.
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)
You might also extract the process of reading input and getting a sum of the float values:
let rec read_lines () =
match In_channel.(input_line stdin) with
| None -> []
| Some line -> line :: read_lines ()
This function will read in a list of lines which we could then process using List
module functions.
# 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
You might rightly say that generating a list isn't strictly required, and you'd be right, so OCaml provides a module for lazy sequences: 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