aboutsummaryrefslogtreecommitdiffstats
path: root/system/doc/getting_started/records_macros.xml
diff options
context:
space:
mode:
Diffstat (limited to 'system/doc/getting_started/records_macros.xml')
-rw-r--r--system/doc/getting_started/records_macros.xml328
1 files changed, 328 insertions, 0 deletions
diff --git a/system/doc/getting_started/records_macros.xml b/system/doc/getting_started/records_macros.xml
new file mode 100644
index 0000000000..45617f0183
--- /dev/null
+++ b/system/doc/getting_started/records_macros.xml
@@ -0,0 +1,328 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2003</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>Records and Macros</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>record_macros.xml</file>
+ </header>
+ <p>Larger programs are usually written as a collection of files with
+ a well defined interface between the various parts.</p>
+
+ <section>
+ <title>The Larger Example Divided into Several Files</title>
+ <p>To illustrate this, we will divide the messenger example from
+ the previous chapter into five files.</p>
+ <taglist>
+ <tag><c>mess_config.hrl</c></tag>
+ <item>header file for configuration data</item>
+ <tag><c>mess_interface.hrl</c></tag>
+ <item>interface definitions between the client and the messenger</item>
+ <tag><c>user_interface.erl</c></tag>
+ <item>functions for the user interface</item>
+ <tag><c>mess_client.erl</c></tag>
+ <item>functions for the client side of the messenger</item>
+ <tag><c>mess_server.erl</c></tag>
+ <item>functions for the server side of the messenger</item>
+ </taglist>
+ <p>While doing this we will also clean up the message passing
+ interface between the shell, the client and the server and define
+ it using <em>records</em>, we will also introduce <em>macros</em>.</p>
+ <code type="none">
+%%%----FILE mess_config.hrl----
+
+%%% Configure the location of the server node,
+-define(server_node, messenger@super).
+
+%%%----END FILE----</code>
+ <code type="none">
+%%%----FILE mess_interface.hrl----
+
+%%% Message interface between client and server and client shell for
+%%% messenger program
+
+%%%Messages from Client to server received in server/1 function.
+-record(logon,{client_pid, username}).
+-record(message,{client_pid, to_name, message}).
+%%% {'EXIT', ClientPid, Reason} (client terminated or unreachable.
+
+%%% Messages from Server to Client, received in await_result/0 function
+-record(abort_client,{message}).
+%%% Messages are: user_exists_at_other_node,
+%%% you_are_not_logged_on
+-record(server_reply,{message}).
+%%% Messages are: logged_on
+%%% receiver_not_found
+%%% sent (Message has been sent (no guarantee)
+%%% Messages from Server to Client received in client/1 function
+-record(message_from,{from_name, message}).
+
+%%% Messages from shell to Client received in client/1 function
+%%% spawn(mess_client, client, [server_node(), Name])
+-record(message_to,{to_name, message}).
+%%% logoff
+
+%%%----END FILE----</code>
+ <code type="none">
+%%%----FILE user_interface.erl----
+
+%%% User interface to the messenger program
+%%% login(Name)
+%%% One user at a time can log in from each Erlang node in the
+%%% system messenger: and choose a suitable Name. If the Name
+%%% is already logged in at another node or if someone else is
+%%% already logged in at the same node, login will be rejected
+%%% with a suitable error message.
+
+%%% logoff()
+%%% Logs off anybody at at node
+
+%%% message(ToName, Message)
+%%% sends Message to ToName. Error messages if the user of this
+%%% function is not logged on or if ToName is not logged on at
+%%% any node.
+
+-module(user_interface).
+-export([logon/1, logoff/0, message/2]).
+-include("mess_interface.hrl").
+-include("mess_config.hrl").
+
+logon(Name) ->
+ case whereis(mess_client) of
+ undefined ->
+ register(mess_client,
+ spawn(mess_client, client, [?server_node, Name]));
+ _ -> already_logged_on
+ end.
+
+logoff() ->
+ mess_client ! logoff.
+
+message(ToName, Message) ->
+ case whereis(mess_client) of % Test if the client is running
+ undefined ->
+ not_logged_on;
+ _ -> mess_client ! #message_to{to_name=ToName, message=Message},
+ ok
+end.
+
+%%%----END FILE----</code>
+ <code type="none">
+%%%----FILE mess_client.erl----
+
+%%% The client process which runs on each user node
+
+-module(mess_client).
+-export([client/2]).
+-include("mess_interface.hrl").
+
+client(Server_Node, Name) ->
+ {messenger, Server_Node} ! #logon{client_pid=self(), username=Name},
+ await_result(),
+ client(Server_Node).
+
+client(Server_Node) ->
+ receive
+ logoff ->
+ exit(normal);
+ #message_to{to_name=ToName, message=Message} ->
+ {messenger, Server_Node} !
+ #message{client_pid=self(), to_name=ToName, message=Message},
+ await_result();
+ {message_from, FromName, Message} ->
+ io:format("Message from ~p: ~p~n", [FromName, Message])
+ end,
+ client(Server_Node).
+
+%%% wait for a response from the server
+await_result() ->
+ receive
+ #abort_client{message=Why} ->
+ io:format("~p~n", [Why]),
+ exit(normal);
+ #server_reply{message=What} ->
+ io:format("~p~n", [What])
+ after 5000 ->
+ io:format("No response from server~n", []),
+ exit(timeout)
+ end.
+
+%%%----END FILE---</code>
+ <code type="none">
+%%%----FILE mess_server.erl----
+
+%%% This is the server process of the messenger service
+
+-module(mess_server).
+-export([start_server/0, server/0]).
+-include("mess_interface.hrl").
+
+server() ->
+ process_flag(trap_exit, true),
+ server([]).
+
+%%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
+server(User_List) ->
+ io:format("User list = ~p~n", [User_List]),
+ receive
+ #logon{client_pid=From, username=Name} ->
+ New_User_List = server_logon(From, Name, User_List),
+ server(New_User_List);
+ {'EXIT', From, _} ->
+ New_User_List = server_logoff(From, User_List),
+ server(New_User_List);
+ #message{client_pid=From, to_name=To, message=Message} ->
+ server_transfer(From, To, Message, User_List),
+ server(User_List)
+ end.
+
+%%% Start the server
+start_server() ->
+ register(messenger, spawn(?MODULE, server, [])).
+
+%%% Server adds a new user to the user list
+server_logon(From, Name, User_List) ->
+ %% check if logged on anywhere else
+ case lists:keymember(Name, 2, User_List) of
+ true ->
+ From ! #abort_client{message=user_exists_at_other_node},
+ User_List;
+ false ->
+ From ! #server_reply{message=logged_on},
+ link(From),
+ [{From, Name} | User_List] %add user to the list
+ end.
+
+%%% Server deletes a user from the user list
+server_logoff(From, User_List) ->
+ lists:keydelete(From, 1, User_List).
+
+%%% Server transfers a message between user
+server_transfer(From, To, Message, User_List) ->
+ %% check that the user is logged on and who he is
+ case lists:keysearch(From, 1, User_List) of
+ false ->
+ From ! #abort_client{message=you_are_not_logged_on};
+ {value, {_, Name}} ->
+ server_transfer(From, Name, To, Message, User_List)
+ end.
+%%% If the user exists, send the message
+server_transfer(From, Name, To, Message, User_List) ->
+ %% Find the receiver and send the message
+ case lists:keysearch(To, 2, User_List) of
+ false ->
+ From ! #server_reply{message=receiver_not_found};
+ {value, {ToPid, To}} ->
+ ToPid ! #message_from{from_name=Name, message=Message},
+ From ! #server_reply{message=sent}
+ end.
+
+%%%----END FILE---</code>
+ </section>
+
+ <section>
+ <title>Header Files</title>
+ <p>You will see some files above with extension <c>.hrl</c>. These
+ are header files which are included in the <c>.erl</c> files by:</p>
+ <code type="none">
+-include("File_Name").</code>
+ <p>for example:</p>
+ <code type="none">
+-include("mess_interface.hrl").</code>
+ <p>In our case above the file is fetched from the same directory as
+ all the other files in the messenger example. (*manual*).</p>
+ <p>.hrl files can contain any valid Erlang code but are most often
+ used for record and macro definitions.</p>
+ </section>
+
+ <section>
+ <title>Records</title>
+ <p>A record is defined as:</p>
+ <code type="none">
+-record(name_of_record,{field_name1, field_name2, field_name3, ......}).</code>
+ <p>For example:</p>
+ <code type="none">
+-record(message_to,{to_name, message}).</code>
+ <p>This is exactly equivalent to:</p>
+ <code type="none">
+{message_to, To_Name, Message}</code>
+ <p>Creating record, is best illustrated by an example:</p>
+ <code type="none">
+#message_to{message="hello", to_name=fred)</code>
+ <p>This will create:</p>
+ <code type="none">
+{message_to, fred, "hello"}</code>
+ <p>Note that you don't have to worry about the order you assign
+ values to the various parts of the records when you create it.
+ The advantage of using records is that by placing their
+ definitions in header files you can conveniently define
+ interfaces which are easy to change. For example, if you want to
+ add a new field to the record, you will only have to change
+ the code where the new field is used and not at every place
+ the record is referred to. If you leave out a field when creating
+ a record, it will get the value of the atom undefined. (*manual*)</p>
+ <p>Pattern matching with records is very similar to creating
+ records. For example inside a <c>case</c> or <c>receive</c>:</p>
+ <code type="none">
+#message_to{to_name=ToName, message=Message} -></code>
+ <p>is the same as:</p>
+ <code type="none">
+{message_to, ToName, Message}</code>
+ </section>
+
+ <section>
+ <title>Macros</title>
+ <p>The other thing we have added to the messenger is a macro.
+ The file <c>mess_config.hrl</c> contains the definition:</p>
+ <code type="none">
+%%% Configure the location of the server node,
+-define(server_node, messenger@super).</code>
+ <p>We include this file in mess_server.erl:</p>
+ <code type="none">
+-include("mess_config.hrl").</code>
+ <p>Every occurrence of <c>?server_node</c> in <c>mess_server.erl</c>
+ will now be replaced by <c>messenger@super</c>.</p>
+ <p>The other place a macro is used is when we spawn the server
+ process:</p>
+ <code type="none">
+spawn(?MODULE, server, [])</code>
+ <p>This is a standard macro (i.e. defined by the system, not
+ the user). <c>?MODULE</c> is always replaced by the name of
+ current module (i.e. the <c>-module</c> definition near the start
+ of the file). There are more advanced ways of using macros with,
+ for example parameters (*manual*).</p>
+ <p>The three Erlang (<c>.erl</c>) files in the messenger example are
+ individually compiled into object code file (<c>.beam</c>).
+ The Erlang system loads and links these files into the system
+ when they are referred to during execution of the code. In our
+ case we simply have put them in the same directory which is our
+ current working directory (i.e. the place we have done "cd" to).
+ There are ways of putting the <c>.beam</c> files in other
+ directories.</p>
+ <p>In the messenger example, no assumptions have been made about
+ what the message being sent is. It could be any valid Erlang term.</p>
+ </section>
+</chapter>
+