Erlang 记录项列表

6
例如,我有一个 Erlang 记录:
-record(state, {clients
            }).

我能否从客户端字段列表中创建?

这样我可以像普通列表一样在客户端字段中保留它吗?而且如何向此列表中添加一些值?

谢谢。

3个回答

7
也许你的意思是这样的:
-module(reclist).
-export([empty_state/0, some_state/0, 
         add_client/1, del_client/1,
         get_clients/1]).

-record(state, 
     {    
          clients = []   ::[pos_integer()],
          dbname         ::char()
     }).    

empty_state() ->
     #state{}.

some_state() ->
     #state{
          clients = [1,2,3],
          dbname  = "QA"}.

del_client(Client) ->
     S = some_state(),
     C = S#state.clients,
     S#state{clients = lists:delete(Client, C)}. 

add_client(Client) ->
     S = some_state(),
     C = S#state.clients,
     S#state{clients = [Client|C]}.

get_clients(#state{clients = C, dbname = _D}) ->
     C.

测试:

1> reclist:empty_state().
{state,[],undefined}
2> reclist:some_state(). 
{state,[1,2,3],"QA"}
3> reclist:add_client(4).
{state,[4,1,2,3],"QA"}
4> reclist:del_client(2).
{state,[1,3],"QA"}

::[pos_integer()] 的意思是该字段的类型是一个正整数值列表,从 1 开始;这是分析工具 dialyzer 在执行类型检查时的提示。

Erlang 还允许您在记录上使用模式匹配:

5> reclist:get_clients(reclist:some_state()).
[1,2,3]

更多阅读:


@JUST MY correct OPINION的回答让我想起我喜欢Haskell是如何获取数据类型中字段的值。

这里有一个数据类型的定义,从Learn You a Haskell for Great Good!中借鉴了记录语法:

data Car = Car {company :: String 
               ,model   :: String
               ,year    :: Int
               } deriving (Show)

它创建了函数companymodelyear,用于查找数据类型中的字段。我们首先创建一辆新车:
ghci> Car "Toyota" "Supra" 2005
Car {company = "Toyota", model = "Supra", year = 2005}

或者,使用记录语法(字段的顺序不重要):
ghci> Car {model = "Supra", year = 2005, company = "Toyota"}
Car {company = "Toyota", model = "Supra", year = 2005}
ghci> let supra = Car {model = "Supra", year = 2005, company = "Toyota"}
ghci> year supra
2005

我们甚至可以使用模式匹配:
ghci> let (Car {company = c, model = m, year = y}) = supra
ghci> "This " ++ c ++ " " ++ m ++ " was made in " ++ show y
"This Toyota Supra was made in 2005"

我记得有人尝试在Erlang中实现类似于Haskell记录语法的东西,但不确定是否成功。
关于这些尝试的一些帖子: 看起来LFE使用宏,这与Scheme提供的方式相似(例如Racket),当您想要创建某个结构的新值时:
> (define-struct car (company model year))
> (define supra (make-car "Toyota" "Supra" 2005))
> (car-model supra)
"Supra"

我希望未来能够有类似Haskell记录语法的东西,这将非常实用和方便。


哎呀,这是 dialyzer。更多关于 Erlang Records 的内容。 - YasirA
哦,应该是clients = [] :: [pos_integer()] - YasirA
你不能编辑自己的帖子并更新它,以显示你的2600积分吗? - ndim
@ndim:我可以这样做。我只是习惯于在注释中介绍清晰度并提及可能存在的错误,因为有一次当我编辑了一篇帖子8次而变成CW时,我失去了至少70个声望值。如果有一些评论,我会稍后编辑帖子并将它们包括进来。;-) - YasirA

3
Yasir的回答是正确的,但我会向您展示它为什么能够正常工作,以便您更好地理解记录。
在Erlang中,记录是一种hack(而且相当丑陋)。使用Yasir的答案中的记录定义...
-record(state, 
     {    
          clients = []   ::[pos_integer()],
          dbname         ::char()
     }).

当你使用#state{}实例化它(就像Yasir在empty_state/0函数中所做的那样),你实际上得到的是这个:
{state, [], undefined}

换句话说,你的“记录”只是一个以记录名称(在本例中为state)标记的元组,后面跟着记录的内容。在BEAM内部,没有记录。它只是另一个包含Erlang数据类型的元组。这就是理解事物如何工作的关键(也是记录的局限性所在)。 现在当Yasir这样做时...
add_client(Client) ->
     S = some_state(),
     C = S#state.clients,
     S#state{clients = [Client|C]}.

对于S#state.clients位,内部转化的代码看起来是element(2,S)。换句话说,您正在使用标准元组操作函数。 S#state.clients只是一种符号化的方式,以此让您知道第2个元素实际上是什么。这是一种语法上的改进,比以容易出错的方式跟踪元组中的单个字段要好。

现在针对最后一个S#state{clients = [Client|C]}位,我不确定在幕后生成了什么代码,但很可能就是直接做等同于{state,[Client|C],element(3,S)} 的简单操作。它:

  • 将名称为记录的新元组标记(提供为#state),
  • 复制来自S的元素(由S#部分指定),
  • 除了被{clients = [Client|C]}覆盖的clients部分。

所有这些魔法都是通过预处理技巧在幕后完成的。

了解记录在幕后如何工作有助于理解使用记录编写的代码以及理解如何自己使用它们(更不用说了解为什么似乎“有意义”的事情在记录中不起作用 - 因为它们实际上不存在于抽象机器中......但)。


1

如果您只是在状态中添加或删除单个客户列表项,那么您可以使用宏来减少输入。

-record(state, {clients = [] }).

-define(AddClientToState(Client,State),
    State#state{clients = lists:append([Client], State#state.clients) } ).

-define(RemoveClientFromState(Client,State),
    State#state{clients = lists:delete(Client, State#state.clients) } ).

这是一个演示的测试脚本:

#!/usr/bin/env escript

-record(state, {clients = [] }).

-define(AddClientToState(Client,State),
    State#state{clients = lists:append([Client], State#state.clients)}  ).

-define(RemoveClientFromState(Client,State),
    State#state{clients = lists:delete(Client, State#state.clients)}    ).  


main(_) ->

    %Start with a state with a empty list of clients.
    State0 = #state{},
    io:format("Empty State: ~p~n",[State0]),

    %Add foo to the list
    State1 = ?AddClientToState(foo,State0),
    io:format("State after adding foo: ~p~n",[State1]),

    %Add bar to the list.
    State2 = ?AddClientToState(bar,State1),
    io:format("State after adding bar:  ~p~n",[State2]),

    %Add baz to the list.
    State3 = ?AddClientToState(baz,State2),
    io:format("State after adding baz:  ~p~n",[State3]),

    %Remove bar from the list.
    State4 = ?RemoveClientFromState(bar,State3),
    io:format("State after removing bar:  ~p~n",[State4]).

结果:

Empty State: {state,[]}
State after adding foo: {state,[foo]}
State after adding bar:  {state,[bar,foo]}
State after adding baz:  {state,[baz,bar,foo]}
State after removing bar:  {state,[baz,foo]}

1
在这种情况下,我更喜欢使用函数而不是宏。这样可以使代码更易于跟踪、重构和测试。 - Adam Lindberg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接