diff options
Diffstat (limited to 'system/doc/getting_started/records_macros.xml')
-rw-r--r-- | system/doc/getting_started/records_macros.xml | 328 |
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> + |