例如,我有一个 Erlang 记录:
-record(state, {clients
}).
我能否从客户端字段列表中创建?
这样我可以像普通列表一样在客户端字段中保留它吗?而且如何向此列表中添加一些值?
谢谢。
-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)
company
、model
和year
,用于查找数据类型中的字段。我们首先创建一辆新车: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"
> (define-struct car (company model year))
> (define supra (make-car "Toyota" "Supra" 2005))
> (car-model supra)
"Supra"
我希望未来能够有类似Haskell记录语法的东西,这将非常实用和方便。
-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
部分。所有这些魔法都是通过预处理技巧在幕后完成的。
了解记录在幕后如何工作有助于理解使用记录编写的代码以及理解如何自己使用它们(更不用说了解为什么似乎“有意义”的事情在记录中不起作用 - 因为它们实际上不存在于抽象机器中......但)。
如果您只是在状态中添加或删除单个客户列表项,那么您可以使用宏来减少输入。
-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]}
dialyzer
。更多关于 Erlang Records 的内容。 - YasirAclients = [] :: [pos_integer()]
。 - YasirA