Erlang Central

Reading Lines from a File

Revision as of 02:38, 4 September 2006 by Bfulgham (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Contents

Problem

You wish to read lines of text from an Io Device.

Solution

Erlang offers the get_line function for reading lines of text from an IoDevice. The following are some useful procedures which use get_line to make it easy to process an entire file, one line at a time. This code was adapted from the comp.lang.scheme thread, How to read lines from a text file.

The following is a straightforward implementation which allows the programmer to provide a procedure which will be called with each new line read from a file.

for_each_line_in_file(Name, Proc, Mode, Accum0) ->
    {ok, Device} = file:open(Name, Mode),
    for_each_line(Device, Proc, Accum0).

for_each_line(Device, Proc, Accum) ->
    case io:get_line(Device, "") of
        eof  -> file:close(Device), Accum;
        Line -> NewAccum = Proc(Line, Accum),
                    for_each_line(Device, Proc, NewAccum)
    end.

For documentation of the optional mode parameters, see the Erlang file module documentation.

Example

The following will display the source code from the file complex.erl, with line numbers.

1> A = for_each_line_in_file("complex.erl",
1>   fun(X, Count) -> io:fwrite("~10B: ~s", [Count, X]),
1>       Count + 1 end, [read], 0).

Yielding:

         0: -module(complex).
         1: -export([make/2, is_complex/1, add/2, sub/2, mult/2, divide/2,
         2:          get_real/1, get_imaginary/1]).
         3: 
         4: -record( complex, {real, imaginary}).
         5: 
         6: is_complex(X) when record(X, complex) -> true;
         7: is_complex(_) -> false.
         8: 
[ ... ]
        40:           ((A#complex.imaginary * B#complex.real)
        41:             - (A#complex.real * B#complex.imaginary)) / Divisor).
        42: 
        43: get_real(X) -> X#complex.real.
        44: 
        45: get_imaginary(X) -> X#complex.imaginary.
ok

Discussion

Notice that line numbering is achieved without needing to mutate a variable (an important skill in a single-assignment language, like Erlang!). The user-supplied procedure simply accepts a line-num argument, and when it's done, returns the next value for that argument. The initial line number is supplied as the last argument to the for_each_line helper function, and the user-supplied function is expected to accept an accumulator (or in this case, a count) variable that holds the current state.

This general pattern is called a fold, which is a functional generalization of iteration. There are many such examples in the Erlang distribution, such as the canonical lists:foldl function for folding over lists.