z_axis
z_axis

Reputation: 8460

Using exceptions to break out of an iteration over the lines of a file

There are some text files which needed to be manipulated line by line. I wrote a withFile function as below:

let withFile fn handle =
    let rec iter_lines fh =
    try
        handle (input_line fh);
        iter_lines fh
    with _ -> close_in fh in
    iter_lines (open_in fn);;

So i can manipulate each file as :

withFile "file1.txt" (fun line -> (*...*))
withFile "file2.txt" (fun line -> (*...*))
...

But I am not sure how to exit elegantly when i do not want to handle all lines. For example:

withFile "file3.txt" (fun line ->
   (*when the line meets some condition, i will exit without handling other lines*)
);

Any suggestion is appreciated !

Upvotes: 4

Views: 511

Answers (1)

Jeffrey Scofield
Jeffrey Scofield

Reputation: 66823

Your function iter_lines isn't tail recursive, which means you will probably exhaust your stack space if you process a very large file this way. The reason it isn't tail recursive is that it has to establish and tear down the try ... with mechanism for catching exceptions.

Other than that, this looks really good to me. This kind of higher-order function is what OCaml (and FP) is all about.

One way to make it tail recursive is to move the exception handling out to the containing function. I would also be more specific about the exception that you want to handle. So you get this:

let withFile fn handle =
    let rec iter_lines fh =
        handle (input_line fh);
        iter_lines fh
    in
    let fh = open_in fn in
    try iter_lines fh
    with End_of_file -> close_in fh

If you want to be able to exit early, one easy way is to have your handle function return a boolean that tells whether to continue processing lines or not. You would end up with something like this:

let withFile fn handle =
    let rec iter_lines fh =
        if handle (input_line fh) then
            iter_lines fh
    in
    let fh = open_in fn in
    try iter_lines fh
    with End_of_file -> close_in fh

If you want to be able to use an exception to exit early, you'd need to catch all exceptions in withFile, close the file, then re-raise any exception other than End_of_file. This gives you code that looks like this:

let withFile fn handle =
    let rec iter_lines fh =
        handle (input_line fh);
        iter_lines fh
    in
    let fh = open_in fn in
    try iter_lines fh
    with e ->
        (close_in fh; if e <> End_of_file then raise e)

Upvotes: 6

Related Questions