Erlang.org Articles RSS http://www.erlang.org/rss/articles Hot and fresh Erlang.org RSS articles en Articles http://www.erlang.org/article/16 Fri, 30 Apr 2010 11:31:02 GMT <p><p> To the right are all possible ways to browse and filter the articles.</p> <p> Most important top tags:</p> <dl> <dt> <a href="/article/tag/euc">euc</a></dt> <dd> Erlang User Conferences</dd> <dt> <a href="/article/tag/workshop">workshop</a></dt> <dd> ACM SIGPLAN Erlang Workshops</dd> <dt> <a href="/article/tag/examples">examples</a></dt> <dd> Erlang programming code examples</dd> </dl> <p> Browse <a href="/article/year">by year</a> for this year&#39;s reverse chronological list.</p> </p> Articles Examples http://www.erlang.org/article/13 Thu, 29 Apr 2010 16:22:00 GMT <p><p> The tag <a href="/article/tag/examples">examples</a> contain as you might expect some examples:</p> </p> Articles Small Examples http://www.erlang.org/article/12 Thu, 29 Apr 2010 16:21:00 GMT <p><p> The tag <a href="/article/tag/small_examples">small_examples</a> contains a number of small programs. Each program is contained in a single file. Only modules in the standard distribution are used. Each program has a short description and a boxed example showing which commands must be given in order to run the program.</p> </p> Articles Klacke's examples http://www.erlang.org/article/14 Thu, 29 Apr 2010 16:20:00 GMT <p><p> <i>The original article and examples was written by Claes Wikstrom in 1998. In april 2008, We removed an example that no longer works and revised the examples to eliminate warnings and use the modern type tests (<code>when is_list(List)</code> instead of <code>when list(List)</code>). In april 2010 we removed the GS example since that GUI library should be deprecated.<br /> </i></p> <p> In this document I aim to provide a number of small examples of nice Erlang programs. The goal of these examples is to let the reader get started immediataly doing some interesting stuff with the erlang programming environment. The document assumes some knowledge about erlang programming in general.</p> <h1> Programs in this document</h1> <p> For more hardcore like people who just want to see the source code, I provide a list of all Erlang examples described and commented in this document immediataly.</p> <dl> <dt> <a href="/upload/klacke_examples/count_chars.erl">count_chars.erl</a></dt> <dd> Count the number of &#39;x&#39; chars in a file. Different versions.</dd> <dt> <a href="/upload/klacke_examples/wc.erl">wc.erl</a></dt> <dd> A small wc look alike.</dd> <dt> <a href="/upload/klacke_examples/find.erl">find.erl</a></dt> <dd> A small find look alike.</dd> <dt> <a href="/upload/klacke_examples/slogger.erl">slogger.erl</a></dt> <dd> A simple term logger. There is also a small <a href="/upload/klacke_examples/test.erl">example</a>.</dd> <dt> <a href="/upload/klacke_examples/glogger.erl">glogger.erl</a></dt> <dd> The same logger again with the <code>gen_server</code> module.</dd> <dt> <a href="/upload/klacke_examples/klib.erl">klib.erl</a></dt> <dd> My personal goodies library.</dd> <dt> <a href="/upload/klacke_examples/time_server.erl">time_server.erl</a></dt> <dd> A TCP/IP time server.</dd> <dt> <a href="/upload/klacke_examples/chargen.erl">chargen.erl</a></dt> <dd> A threaded <code>chargen</code> server.</dd> <dt> <a href="/upload/klacke_examples/ftpd.erl">ftpd.erl</a></dt> <dd> A complete ftpd.</dd> </dl> <p> We start off with some examples of file handling. Most off the stuff related to file handling resides in the module called <a href="http://www.erlang.org/doc/man/file.html">file</a>. The easist method of finding out which functions that are available in the <a href="http://www.erlang.org/doc/man/file.html">file</a> module is to read the manual page. The manual page is available on UNIX systems by invoking the command <code>% erl -man file</code> at the UNIX shell prompt. This of course assumes a correctly installed Erlang system. On a Windows box, there is no manual reader but exactly the same information is available in HTML format.</p> </p> Articles File IO http://www.erlang.org/article/11 Thu, 29 Apr 2010 16:05:29 GMT <p><center> <p> <em>Next: <a href="/article/15">Socket IO</a></em></p> </center> <p> <i>The original article and examples was written by Claes Wikstrom in 1998. In april 2008, We removed an example that no longer works and revised the examples to eliminate warnings and use the modern type tests (<code>when is_list(List)</code> instead of <code>when list(List)</code>). In april 2010 we removed the GS example since that GUI library should be deprecated.</i></p> <h1> Counting x&#39;es</h1> <p> Our first example is real simple, the idea is to open a file read the contents from the file and count the number of characters in the file. A file can be in either of two modes, <code>binary</code> or normal. In all our examples here all IO will be in <code>binary</code> mode. This means that all IO that comes from the file are Erlang <code>binary</code> data objects. So lets create a new Erlang module. We do that by invoking our favourite editor on a file, let&#39;s call it &quot;count_chars.erl&quot;. If we use the famous emacs editor, we can get a whole lot of support in our Erlang programming. Turn on all the bells and whistles, font-lock-mode and everything. Anyway, The head of the file shall be:</p> <pre class="brush:erlang;">%%%---------------------------------------------------------------------- %%% File&nbsp;&nbsp;&nbsp; : count_chars.erl %%% Author&nbsp; : Claes Wikstrom [klacke@bluetail.com] %%% Purpose : Count the x chars in a file %%% Created : 20 Oct 1998 by Claes Wikstrom [klacke@bluetail.com] %%%---------------------------------------------------------------------- -module(count_chars). -author(&#39;klacke@bluetail.com&#39;). -export([file/1]). </pre> <p> The actual code to open the file is contained in a function <code>file/1</code>. This function has a minor flaw which we shall soon rectify. But here goes:</p> <pre class="brush:erlang;first-line:13;">file(Fname) -&gt; &nbsp;&nbsp;&nbsp; case file:open(Fname, [read, raw, binary]) of &nbsp;{ok, Fd} -&gt; &nbsp;&nbsp;&nbsp;&nbsp; scan_file(Fd, 0, file:read(Fd, 1024)); &nbsp;{error, Reason} -&gt; &nbsp;&nbsp;&nbsp;&nbsp; {error, Reason} &nbsp;&nbsp;&nbsp; end. scan_file(Fd, Occurs, {ok, Binary}) -&gt; &nbsp;&nbsp;&nbsp; scan_file(Fd, Occurs + count_x(Binary), file:read(Fd, 1024)); scan_file(Fd, Occurs, eof) -&gt; &nbsp;&nbsp;&nbsp; file:close(Fd), &nbsp;&nbsp;&nbsp; Occurs; scan_file(Fd, _Occurs, {error, Reason}) -&gt; &nbsp;&nbsp;&nbsp; file:close(Fd), &nbsp;&nbsp;&nbsp; {error, Reason}. </pre> <p> The <code>file/1</code> function opens the file and reads the characters in chunks of 1 k. For each chunk it calls a function <code>count_x/1</code> to do the real counting. This function transforms each binary to a list of characters in order to be able to traverse and count. We have:</p> <pre class="brush:erlang;first-line:30;">count_x(Bin) -&gt; &nbsp;&nbsp;&nbsp; count_x(binary_to_list(Bin), 0). count_x([$x|Tail], Ack) -&gt; &nbsp;&nbsp;&nbsp; count_x(Tail, Ack+1); count_x([_|Tail], Ack) -&gt; &nbsp;&nbsp;&nbsp; count_x(Tail, Ack); count_x([], Ack) -&gt; &nbsp;&nbsp;&nbsp; Ack. </pre> <p> Now to compile and run this code we invoke the erlang system at the unix prompt and enter the following commands:</p> <pre class="brush:erlang;gutter:false;toolbar:false;">% erl Erlang (JAM) emulator version 4.7.3 Eshell V4.7.3 (abort with ^G) 1&gt; c:c(count_chars). {ok,count_chars} 2&gt; count_chars:file(&quot;count_chars.erl&quot;). 17 3&gt; </pre> <p> So the file &quot;count_chars.erl&quot; contains 17 x&#39;es.</p> <p> The abovementioned flaw is that we do not close the file in the same function where we opened the file. In this particular case we close it in the function just below, but it is a general good rule to release resources in the source code where they are allocated. One way to rectify this here is to rewrite the code like:</p> <pre class="brush:erlang;first-line:13;">file(Fname) -&gt; &nbsp;&nbsp;&nbsp; case file:open(Fname, [read, raw, binary]) of &nbsp;{ok, Fd} -&gt; &nbsp;&nbsp;&nbsp;&nbsp; Res = scan_file(Fd, 0, file:read(Fd, 1024)), &nbsp;&nbsp;&nbsp;&nbsp; file:close(Fd), &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Res; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .... </pre> <p> However we can do better, we can write a general purpose function <code>with_file</code> which feed a user provided <code>Fun</code> with chunks of data until done.</p> <h1> General purpose file IO</h1> <p> The technique of generalizing a common pattern of functionality into a framework which uses higher order functions to do the work is a powerful lines-of-code saver. So the general purpose function is:</p> <pre class="brush:erlang;first-line:19;">with_file(File, Fun, Initial) -&gt; case file:open(File, [read, raw, binary]) of {ok, Fd} -&gt; Res = feed(Fd, file:read(Fd, 1024), Fun, Initial), file:close(Fd), Res; {error, Reason} -&gt; {error, Reason} end. feed(Fd, {ok, Bin}, Fun, Farg) -&gt; case Fun(Bin, Farg) of {done, Res} -&gt; Res; {more, Ack} -&gt; feed(Fd, file:read(Fd, 1024), Fun, Ack) end; feed(Fd, eof, Fun, Ack) -&gt; Ack; feed(_Fd, {error, Reason}, _Fun, _Ack) -&gt; {error, Reason}. </pre> <p> The user provides three arguments to the function.</p> <dl> <dt> <code>File</code></dt> <dd> the name of the file to work on.</dd> <dt> <code>Fun</code></dt> <dd> a functional object which must return either of <code>{more, Ack}</code> or <code>{done, Res}</code> in order to guide the <code>feed</code> loop what to do.</dd> <dd> the initial ackumulator parameter to the fun.</dd> <dt> <code>Initial</code></dt> </dl> <p> This code is typical general purpose code and we shall add it to a library of &quot;nice to have functions&quot;. We call this library <a href="/upload/klacke_examples/klib.erl">klib.erl</a>.</p> <p> Now the original <code>count_chars:file/1</code> function becomes much shorter.</p> <pre class="brush:erlang;first-line:39;">file1(File) -&gt; &nbsp;&nbsp;&nbsp; F = fun(Bin, Int) -&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {more, count_x(Bin) + Int} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end, &nbsp;&nbsp;&nbsp; klib:with_file(File, F, 0). </pre> <p> As a matter of fact we can do even better than that, we can use a function defined in &quot;lists.erl&quot; which folds over a list. This is admittedly not code that ought to be in the first section of a beginner guide but we provide it anyway. We have:</p> <pre class="brush:erlang;first-line:45;">file2(File) -&gt; &nbsp;&nbsp;&nbsp; {ok,B} = file:read_file(File), &nbsp;&nbsp;&nbsp; lists:foldl(fun($x, Ack) -&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1 + Ack; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (_, Ack) -&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Ack &nbsp;&nbsp;&nbsp; end, 0, binary_to_list(B)).</pre> <p> Now we have three version of the same function say we want to meassure how fast they are. The mudule &quot;timer&quot; has a function called &quot;tc(Mod, Fun, Args)&quot; which executes a function and times it. Lets do it at the shell prompt:</p> <pre class="brush:erlang;gutter:false;">32&gt; timer:tc(count_chars, file, [&quot;index.html&quot;]). {59554,68} 33&gt;&nbsp; timer:tc(count_chars, file1, [&quot;index.html&quot;]). {69939,68} 34&gt; timer:tc(count_chars, file2, [&quot;index.html&quot;]). {335579,68} </pre> <p> &nbsp;The <code>tc/3</code> function returns a tuple <code>{MicroSeconds, Result</code> where <code>MicroSeconds</code> is the number of micro seconds it took to eavluate the function and <code>Result</code> is the avaluation result.</p> <p> We see that the first (and most complicated) version is the fastest. It takes 63 milli seconds whereas the next version which uses the <code>with_file/3</code> function takes 69 milli seconds. On the other hand the most beautiful version, <code>file2/1</code> which folds over the entire lists takes an awful 337 milli seconds.</p> <h1> Word count</h1> <p> We continue with a program which is a little bit more useful than just counting the &#39;x&#39; characters in a random file. We want to write a program that counts the number of words, chars and lines in a file. We wish the function to have an interface We wish the module to have an interface</p> <p> <code>wc:file(File)</code><br /> <code>wc:files(FileList)</code></p> <pre class="brush:erlang;">-module(wc). -author(&#39;klacke@erix.ericsson.se&#39;). -import(count_chars, [with_file/3]). -import(lists, [map/2, foreach/2]). -export([file/1, files/1]). file(File) -&gt; &nbsp;&nbsp;&nbsp; output([gfile(File)]). gfile(File) -&gt; &nbsp;&nbsp;&nbsp; Fun = fun(Bin, Count) -&gt; &nbsp;&nbsp;&nbsp; count_bin(binary_to_list(Bin), inspace, Count) &nbsp;&nbsp; end, &nbsp;&nbsp;&nbsp; {File, with_file(File, Fun, {0,0,0})}. count_bin([H|T], Where, {C,W,L}) -&gt; &nbsp;&nbsp;&nbsp; case classify_char(H) of &nbsp;newline&nbsp; when Where == inspace -&gt; &nbsp;&nbsp;&nbsp;&nbsp; count_bin(T, inspace, {C+1, W, L+1}); &nbsp;newline when Where == inword -&gt; &nbsp;&nbsp;&nbsp;&nbsp; count_bin(T, inspace, {C+1, W+1, L+1}); &nbsp;space&nbsp; when Where == inspace -&gt; &nbsp;&nbsp;&nbsp;&nbsp; count_bin(T, inspace, {C+1, W, L}); &nbsp;space&nbsp; when Where == inword -&gt; &nbsp;&nbsp;&nbsp;&nbsp; count_bin(T, inspace, {C+1, W+1, L}); &nbsp;char -&gt; &nbsp;&nbsp;&nbsp;&nbsp; count_bin(T, inword, {C+1, W, L}) &nbsp;&nbsp;&nbsp; end; count_bin([], inword, {C, W, L}) -&gt; &nbsp;&nbsp;&nbsp; {more, {C, W+1, L}}; count_bin([], inspace, {C, W, L}) -&gt; &nbsp;&nbsp;&nbsp; {more, {C, W, L}}. classify_char($ ) -&gt; &nbsp;&nbsp;&nbsp; space; classify_char($\t) -&gt; &nbsp;&nbsp;&nbsp; space; classify_char($\n) -&gt; &nbsp;&nbsp;&nbsp; newline; classify_char(_) -&gt; &nbsp;&nbsp;&nbsp; char. files(Files) -&gt; &nbsp;&nbsp;&nbsp; output(map(fun(F) -&gt; gfile(F) end, Files)). output(Counts) -&gt; &nbsp;&nbsp;&nbsp; io:format(&quot;~-25s ~-10s ~-10s ~-10s~n&quot;, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [&quot;file&quot;, &quot;chars&quot;, &quot;words&quot;, &quot;lines&quot;]), &nbsp;&nbsp;&nbsp; foreach(fun({File, {C,W,L}}) -&gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ok = io:format(&quot;~-25s ~-10w ~-10w ~-10w~n&quot;, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [File, C, W, L]) &nbsp;&nbsp;&nbsp;&nbsp; end, Counts). </pre> <p> This function is really not very representable as an example of file IO. All we do is call on the previously define <code>klib:with_file/3</code> function with an appropriate Fun. An example session with the erlang shell is:</p> <pre class="brush:plain;gutter:false;">60&gt; {ok, L} = file:list_dir(&quot;.&quot;), wc:files(L). file&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; chars&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; words&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lines&nbsp;&nbsp;&nbsp;&nbsp; wc.beam&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2079&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 40&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 21&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; count_chars.beam&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1796&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 31&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 22&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; klacke_ex.html~&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6173&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 897&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 219&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 11&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wc.erl~&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 587&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 46&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 27&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; klacke_ex.html.orig&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6173&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 897&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 219&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; klacke_ex.html&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 7642&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1079&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 286&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wc.erl&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1661&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 176&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 73&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; count_chars.erl&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1767&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 198&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 87&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </pre> <h1> Finding files</h1> <p> Next example is a function that &#39;finds&#39; files and does things with the files it finds. For example we might want to look for all erlang files in a directory tree and recompile them. We want to be able to write things like:</p> <pre class="brush:erlang;gutter:false;">find:files(&quot;/home/klacke&quot;, &quot;.*\.erl&quot;, fun(F) -&gt; {File, c:c(File)} end) </pre> <p> In order to find all my erlang files and compile them. We have three arguments.</p> <ul> <li> A top directory where to start the search</li> <li> A regular expression that must match the files.</li> <li> A Fun to perform some action on the files we find.</li> </ul> <p> The source code is:</p> <pre class="brush:erlang;">-module(find). -author(&#39;klacke@erix.ericsson.se&#39;). -include_lib(&quot;kernel/include/file.hrl&quot;). -export([files/3]). %% Top is the Top directory where everything starts %% Re is a regular expression to match for (see module regexp) %% Actions is a Fun to apply to each found file %% Return value is a lists of the return values from the %% Action function %% Example: find:files(&quot;/home/klacke&quot;, %% &quot;.*\.erl&quot;, fun(F) -&gt; {File, c:c(File)} end) %% Will find all erlang files in my top dir, compile them and %% return a long list of {File, CompilationResult} tuples %% If an error occurs, {error, {File, Reason}} is returned %% The Action fun is passed the full long file name as parameter files(Top, Re, Action) -&gt; case file:list_dir(Top) of {ok, Files} -&gt; files(Top, Files, Re, Action, []); {error, Reason} -&gt; {error, {Top, Reason}} end. files(Top, [F|Tail], Re, Action, Ack) -&gt; F2 = Top ++ &quot;/&quot; ++ F, case file:read_file_info(F2) of {ok, FileInfo} when FileInfo#file_info.type == directory -&gt; case files(F2, Re, Action) of {error, Reason} -&gt; {error, Reason}; List -&gt; files(Top, Tail, Re, Action, List ++ Ack) end; {error, Reason} -&gt; {error, {F2, Reason}}; {ok, FileInfo} when FileInfo#file_info.type == regular -&gt; case catch regexp:match(F, Re) of {match, _,_} -&gt; files(Top, Tail, Re, Action, [Action(F2) | Ack]); nomatch -&gt; files(Top, Tail, Re, Action, Ack); {error, Reason} -&gt; {error, {F2, {regexp, Reason}}} end; _Other -&gt; files(Top, Tail, Re, Action, Ack) end; files(_Top, [], _Re, _Action, Ack) -&gt; Ack. </pre> <p> The code includes the <code>file_info</code> record definition from the <code>file.hrl</code> include file. The code is completely straigtforward since it simply reads the <code>file_info</code> records recursively and applies the supplied function. It also includes the &quot;.hrl&quot; file by means of an &quot;include_lib&quot; compiler dirctive.</p> <h1> A simple term logger</h1> <p> In this final section on file IO we provide a simple term logger that writes Erlang terms to a file.</p> <p> The BIF <code>term_to_binary/1</code> produces a binary data object from any term. Such a binary can be written to a file. The reverse operation is <code>binary_to_term/1</code> which can be used to reproduce the orignal term. The format we choose to have on the file is to prepend each term with a four (4) byte length field in order to indicate the length of the actual term. The logger runs as a separate process and we shall provide two different versions of the logger, one written with plain Erlang and the other by utilizing the generic server <code>gen_server</code> module. One function which is used by the logger is the function of transforming an integer to a four byte list and vice versa. This function is added to the <a href="/upload/klacke_examples/klib.erl">klib.erl</a> library. Here is the code:</p> <pre class="brush:erlang;gutter:false;">i32(B) when is_binary(B) -&gt; i32(binary_to_list(B, 1, 4)); i32([X1, X2, X3, X4]) -&gt; (X1 bsl 24) bor (X2 bsl 16) bor (X3 bsl 8) bor X4; i32(Int) when integer(Int) -&gt; [(Int bsr 24) band 255, (Int bsr 16) band 255, (Int bsr 8) band 255, Int band 255]. </pre> <p> It is also nice to be able to read an integer from a file, the code in klib.erl is:</p> <pre class="brush:erlang;gutter:false;">getint32(F) -&gt; {ok, B} = file:read(F, 4), i32(B). </pre> <p> This is what I call an aggressive function. It assumes that everything goes well and crashes hard if for example the read call returns <code>eof</code> or if the read attempt should fail for any other reason. The module exports a number of functions, in particular:</p> <pre class="brush:erlang;">-module(slogger). -author(&#39;klacke@bluetail.com&#39;). -export([start/0, start/1, stop/0, log/1, upread/1, truncate/0]). </pre> <p> We need to be able to stop and start the server. The client functions are:</p> <dl> <dt> <code>log/1</code></dt> <dd> Log a term to the end of the file.</dd> <dt> <code>truncate/0</code></dt> <dd> Truncate the log file.</dd> <dt> <code>upread/1</code></dt> <dd> Read all the terms in the logfile and apply <code>Fun</code> to each and every term.</dd> </dl> <p> The start and stop code is the classical traditional style Erlang start server code. This code has flaws, but we will come back to that in the next session. We have:</p> <pre class="brush:erlang;first-line:6;">-define(LOGFILE, &quot;slog.log&quot;). start() -&gt; start(?LOGFILE). start(F) -&gt; case whereis(?MODULE) of undefined -&gt; register(?MODULE, spawn(?MODULE, loop0, [F])); Pid -&gt; true end. stop()-&gt; req(stop). req(R) -&gt; ?MODULE ! {self(), R}, receive {?MODULE, Reply} -&gt; Reply end. </pre> <p> The loop function that is spawned by the the start function has two part, an initialization part and a loop part. The code is:</p> <pre class="brush:erlang;first-line:29;">loop0(FileName) -&gt; case file:open(FileName, [read, write, raw, binary]) of {ok, Fd} -&gt; {ok, Eof} = file:position(Fd, eof), file:position(Fd, bof), FilePos = position_fd(Fd, 0), maybe_warn(FilePos, Eof), loop(Fd); {error, Reason} -&gt; exit(Reason) end. maybe_warn(FilePos, Eof) -&gt; if FilePos == Eof -&gt; ok; true -&gt; warn(&quot;~w bytes truncated \n&quot;, [Eof - FilePos]) end.</pre> <p> We have added some extra code to check the logfile when it is opened. We need to position the file descriptor to the end of the logfile. To do that we could have called <code>file:position(Fd, eof)</code>, however we need to cater for the case where the last logger term in the previous section was corrupted due to an interrupted write operation. Thw function that does the check is:</p> <pre class="brush:erlang;first-line:50;">position_fd(Fd, LastPos) -&gt; case catch getint32(Fd) of Int when is_integer(Int) -&gt; case file:read(Fd, Int) of {ok, B} when size(B) == Int -&gt; position_fd(Fd, LastPos + 4 + Int); _ -&gt; file:position(Fd, LastPos), file:truncate(Fd) end; _ -&gt; file:position(Fd, LastPos), file:truncate(Fd), LastPos end.</pre> <p> The <code>position_fd/2</code> function returns last file position that was ok. If this is equal to the physical file position, all is ok. So, we are approaching the real code that does the actual logging work. The <code>loop/1</code> server loop.</p> <pre class="brush:erlang;first-line:66;">loop(Fd) -&gt; receive {From, {log, Bin}} -&gt; From ! {?MODULE, log_binary(Fd, Bin)}; {From, {upread, Fun}} -&gt; From ! {?MODULE, upread(Fd, Fun)}; {From, truncate} -&gt; file:position(Fd, bof), file:truncate(Fd), From ! {?MODULE, ok}; {From, stop} -&gt; file:close(Fd), From ! {?MODULE, stopped}, exit(normal) end, loop(Fd).</pre> <p> The <code>truncate</code> and the <code>stop</code> requests are handled immediataly in the loop whereas the request to <code>log</code> and <code>upread</code> are handled by special help functions. First we have the <code>log_binary/2</code> function:</p> <pre class="brush:erlang;first-line:83;">log_binary(Fd, Bin) -&gt; Sz = size(Bin), case file:write(Fd, [i32(Sz), Bin]) of ok -&gt; ok; {error, Reason} -&gt; warn(&quot;Cant&#39;t write logfile ~p &quot;, [Reason]), {error, Reason} end.</pre> <p> The only noticeable thing here is the type of the structure that is passed to <code>file:write/2</code> for IO. It is a list on the form <code>[[int, int, int, int], binary]</code>. This structure will be flattened by the port. This applies to all ports and if the Port supports the so called <code>writev()</code> interface (which is documented elsewhere) the <code>writev()</code> function of the native operating system will be called with an array length of <code>2</code> where the first array will hold the four bytes produced by the call to <code>i32()</code> and the second array will hold the actual binary data.</p> <p> Finally the function to do the upreading, i.e a function that blocks the server for logging and then traverses the log and applies a function to each and every logged item is:</p> <pre class="brush:erlang;first-line:93;">upread(Fd, Fun) -&gt; {ok, Curr} = file:position(Fd, cur), file:position(Fd, bof), upread(Fd, catch get_term(Fd), Fun). upread(Fd, {&#39;EXIT&#39;, _}, Fun) -&gt; ok; upread(Fd, Term, Fun) -&gt; Fun(Term), upread(Fd, catch get_term(Fd), Fun). get_term(Fd) -&gt; I = getint32(Fd), {ok, B} = file:read(Fd, I), binary_to_term(B).</pre> <p> And finally the client functions to access the server:</p> <pre class="brush:erlang;first-line:110;">upread(Fun) -&gt; req({upread, Fun}). truncate() -&gt; req(truncate). log(Term) -&gt; req({log, term_to_binary(Term)}).</pre> <p> A typical little caveat here with the <code>upread/1</code> function is that the client function is sitting in a receive statement waiting for <code>upread/1</code> to perform its work. It is thus not possible to have the <code>upread/1</code> function send all the terms to the client. (Since the client is allready suspended). To do that, we need an auxilliary process. Here is a little example session at the shell prompt:</p> <pre class="brush:erlang;gutter:false;">2&gt; slogger:start(). true 3&gt; slogger:log({abc, &quot;cba&quot;}). ok 4&gt; slogger:log(code:which(slogger)). ok 5&gt; slogger:upread(fun(X) -&gt; io:format(&quot;~p~n&quot;, [X]) end). {abc,&quot;cba&quot;} &quot;/home/super/klacke/doc/examples/slogger.jam&quot; ok 6&gt; </pre> <h1> A simple term logger (again)</h1> <p> In this section we have rewritten the above term logger but this time using the <code>gen_server</code> module as a utility to get help with the process structure.</p> <p> <code>gen_server</code>s are extremely powerful and easy to use. The absolutely easiest way to write a gen server is to invoke the <code>gen_server</code> skeleton from the emacs mode while editing.</p> <p> The entire framework of a &quot;do nothing&quot; gen server is generated and we just fill in the details. This way we get all the goodies that come from gen servers in general. They can be upgraded and downgraded wile running, they can be debugged and traced and they fit into the general <code>application</code> concept of Erlang.</p> <p> The code is available in <a href="/upload/klacke_examples/glogger.erl">glogger.erl</a>.</p> <center> <p> <em>Next: <a href="/article/15">Socket IO</a></em></p> </center> </p> Articles