5.记录和宏 | 5. Records and Macros

5记录和宏

较大的程序通常被编写为一个文件集合,在各个部分之间有一个定义良好的接口。

5.1更大的例子分成几个文件

为了说明这一点,上一节中的信使示例分为以下五个文件:

  • mess_config.hrl配置数据的头文件

  • mess_interface.hrl

客户端和信使之间的接口定义

  • user_interface.erl用户界面的函数

  • mess_client.erl

的客户端的函数。

  • mess_server.erl

的服务器端的函数。

这样做时,shell、客户端和服务器之间的消息传递接口将被清理,并使用记录.同时,介绍:

%%%----FILE mess_config.hrl---- %%% Configure the location of the server node, -define(server_node, messenger@super). %%%----END FILE----

%%%----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----

%%%----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 that 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----

%%%----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---

%%%----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---

5.2头文件

如上所示,一些文件具有扩展名.hrl。这些是.erl通过以下方式包含在文件中的头文件:

-include("File_Name").

例如:

-include("mess_interface.hrl").

在上面的例子中,文件是从messenger示例中所有其他文件的同一目录中获取的。(*手册*)。

.hrl文件可以包含任何有效的Erlang代码,但最常用于记录和宏定义。

5.3记录

记录定义为:

-record(name_of_record,{field_name1, field_name2, field_name3, ......}).

例如:

-record(message_to,{to_name, message}).

这相当于:

{message_to, To_Name, Message}

创建记录最好用一个例子来说明:

#message_to{message="hello", to_name=fred)

这就产生了:

{message_to, fred, "hello"}

请注意,在创建记录时,您不必担心将值分配给记录的各个部分的顺序。使用记录的优点是,通过将其定义放在头文件中,您可以方便地定义易于更改的界面。例如,如果要向记录添加新字段,则只需更改使用新字段的代码,而不是记录引用的每个位置。如果您在创建记录时忽略了一个字段,它将获取原子的值undefined。(*手册*)

与记录模式匹配与创建记录非常相似。例如,在case或者里面receive

#message_to{to_name=ToName, message=Message} ->

这与:

{message_to, ToName, Message}

5.4宏

另一件已被添加到信使是一个宏。该文件mess_config.hrl包含定义:

%%% Configure the location of the server node, -define(server_node, messenger@super).

此文件包含在mess_server.erl*

-include("mess_config.hrl").

?server_nodein的每一次出现mess_server.erl现在都被替换掉了messenger@super

在生成服务器进程时也使用宏:

spawn(?MODULE, server, [])

这是一个标准宏(即由系统定义,而不是由用户定义)。?MODULE总是由当前模块的名称替换(即-module文件起始位置附近的定义)。有更多先进的方式来使用宏,例如参数(*手动*)。

messenger示例中的三个Erlang(.erl)文件分别编译为目标代码文件(.beam)。Erlang系统在执行代码时将这些文件加载​​并链接到系统中。在这种情况下,它们只是放在我们当前的工作目录中(即,您已完成“cd”到的位置)。有些方法可以将.beam文件放入其他目录中。

在信使的例子中,没有任何关于发送消息的假设。它可以是任何有效的Erlang术语。