Friday 22 November 2013

Refactoring

Clearly the business of opening a text file, reading through it line by line, and returning some result is going to be a general process.  Therefore let us factor out the general process from the specific application.  How about this:

The top function to process the file takes a file name, and some function to be applied to each line of the file, and a starting value to be threaded through the various lines of the file. We open the file and then call the sub-function that executes the loop, passing across the stream, and the function, and the starting value.  After this returns we get back the final value, so we can close the file and return our final value, so:

process_file(Filename, Fun, StartValue) ->
    {ok, Stream} = file:open(Filename, [read]),
    FinalValue = process_file_loop(Stream, Fun, StartValue),
    file:close(Stream),
    FinalValue.
 
Our function that executes the loop will require the stream and the function and an initial value.  It attempts to read a line but if no more is available it returns the value it was given, nothing more to be said.  However if it gets a line it can call the supplied function with the line and with the starting value, and it will get back some new value.  It can then call itself with this new value to continue the loop, like this:

process_file_loop(Stream, Fun, Value) ->
    case io:get_line(Stream, "") of
        eof ->
            Value;
        Line ->
            NewValue = Fun(Line, Value),
            process_file_loop(Stream, Fun, NewValue)
    end.

Right, that's the general case code.  In our specific application the function we want to apply is that bit of code that writes the line to the terminal with the line number and then increments the line number. We don't do anything with the line number in the end but it has to be threaded through.  We don't have to define this as a named function - with the syntax fun(...)->... we can create it as an anonymous function just when we need it.  So we set the process in motion by calling the process_file function passing the file name, the function we want to apply, and the starting value which is the line number 1.  So our main() function is this:


main([Filename]) ->
    process_file(Filename, 
         fun(Line, LineNumber)->
             io:format("~4.10.0B ~s", [LineNumber, Line]),
             LineNumber+1
         end,
         1).

Still works?

C:\Users\polly\Erlang>escript listout.erl hello.erl
0001
0002 main([]) ->
0003     io:format("Hello World");
0004 main([Arg]) ->
0005     io:format("Hello ~s", [Arg]).
C:\Users\polly\Erlang>

Yep.  Hey, I just did a lambda.  Thank you madam, your G&T is on its way.

No comments:

Post a Comment