Erlang запись списка предметов - PullRequest
6 голосов
/ 10 февраля 2011

Например, у меня есть запись erlang:

-record(state, {clients
            }).

Могу ли я сделать из списка клиентов поля?

Что я мог бы хранить в клиенте, как в обычном списке? И как я могу добавить некоторые значения в этом списке?

Спасибо.

Ответы [ 3 ]

7 голосов
/ 10 февраля 2011

Может быть, вы имеете в виду что-то вроде:

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

Дальнейшее чтение:


@ ПРОСТО МОЙ правильный ответ ответ заставил меня вспомнить, что мне нравится, как Haskell получает значения полей в типе данных.

Вот определение типа данных, украденного из Learn You 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"

Я помню, что были попытки реализовать что-то похожее на синтаксис записей в Haskell в Erlang, но не уверен, что они были успешными.

Несколько постов, касающихся этих попыток:

Кажется, что LFE использует макросы, которые аналогичны тем, что предоставляет схема (например, Racket ), когда вы хотите создать новое значение некоторой структуры:

> (define-struct car (company model year))
> (define supra (make-car "Toyota" "Supra" 2005))
> (car-model supra)
"Supra"

Я надеюсь, что в будущем у нас будет что-то близкое к синтаксису записей на Haskell, что было бы действительно практически полезным и удобным.

3 голосов
/ 11 февраля 2011

Ответ Ясира правильный, но я собираюсь показать вам, ПОЧЕМУ он работает так, как работает, чтобы вы могли немного лучше понять записи.

Записи в Erlangхак (и довольно уродливый).Используя определение записи из ответа Ясира ...

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

... когда вы создаете его с помощью #state{} (как это сделал Ясир в функции empty_state/0), вы действительно получите следующее:

{state, [], undefined}

То есть ваша «запись» - это просто кортеж, помеченный именем записи (в данном случае state), за которым следует содержимое записи.Внутри самого BEAM нет записи .Это просто еще один кортеж с типами данных Erlang, содержащимися в нем.Это ключ к пониманию того, как все работает (и ограничения записей для загрузки).

Теперь, когда Ясир сделал это ...

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, переопределенной на {clients = [Client|C]}.

Вся эта магия выполняется посредством хака предварительной обработки за кулисами.

Понимание того, как записи работают за кулисами, полезно как для понимания кода, написанного с использованием записей, так и для понимания того, как их использовать самостоятельно (не говоря уже о понимании того, почему вещи, которые кажутся «осмысленными», не работают с записями - поскольку онина самом деле не существует в абстрактной машине ... пока).

1 голос
/ 10 февраля 2011

Если вы только добавляете или удаляете отдельные элементы из списка клиентов в состоянии, которое вы можете сократить при наборе текста с помощью макроса.

-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]}
...