仅使用不可变变量在用户函数编程(Erlang)中存储用户输入信息

时间:2017-07-13 05:01:05

标签: functional-programming erlang

作为Erlang的初学者,我正在编写Erlang编程手册(第2版)。我很难掌握如何使用函数式编程原理来存储和定期更新外部信息(例如间歇性用户输入)。

以我现在的例子为例,我现在处于并发编程部分(第12章)的开头,本书讨论了区域服务器。以下是我的变体。

作为练习,我尝试向该模块添加一种存储用户所有请求的方法。但是,尽管在递归编程方面有一些经验,但在命令式语言的意义上,缺少可变变量似乎在这个特定的实例中瘫痪。

我尝试在SE网站上查找一些相关资源,例如mutable state in functional programmingimmutability in fp但它并没有以实际的方式回答我的问题。我知道我想要完成的工作可以通过使用ETS(甚至数据库),或者使用接收和维护其自身历史的新进程的进程内存来完成。

但我真正想要理解的(以及这个问题的重点)是,如果可以使用通用函数编程原则来实现,而不必使用特定于Erlang的工具。代码段中注释掉的行表示我天真地期待第一步看起来像什么。

  -module(geometry_server4).
  -export([start/0, client/2, loop/0]).

   start() ->
       spawn(geometry_server4, loop, []).

   client(Pid_server, Geom_tuple) ->
       Pid_server ! {self(), Geom_tuple},
       %ok = storerequests(Geom_tuple),
      receive
          {area, Pid_server, Area} -> io:format("Client: Area of ~p is ~p~n", [Geom_tuple, Area]);
          {error, Error} -> io:format("~p~n", [Error])
      end.

  %storerequests(Geom_tuple) -> addtolist(Geom_tuple, get_history()).
  %
  %addtolist(Item, History) ->
  %   [Item | History].
  %get_history() -> ???


  loop() ->
      receive
          {Client, {rectangle, S1, S2}} ->
              Area = S1 * S2,
              Client ! {area, self(), Area},
              loop();
          {Client, {square, S}} ->
              Area = S * S,
              Client ! {area, self(), Area},
              loop();
          {Client, _} ->
              Client ! {error, "invalid parameters"},
              loop()
      end.

根据这本书,这个玩具服务器在终端中被调用为:

1> c(geometry_server4).
2> P = geometry_server4:start().
3> geometry_server4:client(P, {square, 3}).

1 个答案:

答案 0 :(得分:2)

  

但是我真的想要理解(以及这一点   问题)是否可以使用通用功能来完成   编程原则,而不必使用特定于Erlang的工具。

是的,它可以。您可以使用循环变量来存储所谓的状态

首先,有几个初步要点:

  1. 请勿使用行号发布代码。您希望某人能够复制您的代码并将其粘贴到文本编辑器中并能够运行代码。

  2. 在erlang中,按照惯例,您使用 camel case 作为变量名称,例如ServerPid

  3. 为了您自己的理智,请不要使用长度超过两个字母的模块名称。

  4. 考虑将所有服务器代码放在文件的一部分中,并将所有客户端代码放在文件的另一部分中。您的客户端代码位于服务器代码的中间。

  5. -module(my).
    %%-export([setup/1]).
    -compile(export_all).
    %%-include_lib("eunit/include/eunit.hrl").
    %%
    
    start() ->
        spawn(my, loop, [[]]).
    
    loop(History) ->
        receive
            {Client, {rectangle, S1, S2}=Tuple} ->
                Area = S1 * S2,
                Client ! {area, self(), Area},
                loop([Tuple|History]);  %Add Tuple to the history
            {Client, {square, S}=Tuple} ->
                Area = S * S,
                Client ! {area, self(), Area},
                loop([Tuple|History]);
            {Client, history} ->
                Client ! {history, self(), History},
                loop([history|History]);
            {Client, Other} ->
                Client ! {error, self(), "invalid parameters"},
                loop([{error, Other}|History])
        end.
    
    client(ServerPid, Req) ->
        ServerPid ! {self(), Req},
        receive
            Reply -> io:format("~p~n", [Reply])
        end.
    
    
    test() ->
        ServerPid = start(),
        Requests = [
            {rectangle, 2, 3},
            {square, 4},
            history,
            "hello",
            history
        ],
        send_requests(Requests, ServerPid).
    
    send_requests([], _) ->
        done;
    send_requests([Req|Reqs], ServerPid) ->
        client(ServerPid, Req),
        send_requests(Reqs, ServerPid).
    

    在shell中:

    1> c(my).
    {ok,my}
    
    2> my:test().
    {area,<0.64.0>,6}
    {area,<0.64.0>,16}
    {history,<0.64.0>,[{square,4},{rectangle,2,3}]}
    {error,<0.64.0>,"invalid parameters"}
    {history,<0.64.0>,[{error,"hello"},history,{square,4},{rectangle,2,3}]}
    done
    
    3>