Идем сюда на Эрланге.
На базовом уровне доступны две опции. Если вы хотите использовать become
только для изменения поведения данного процесса (см. Пункт 2 списка в разделе 2.1.3), тогда просто нужно вызвать следующий цикл с другой рекурсивной функцией:
loop(State) ->
receive
{normal, Msg} -> loop(State);
{change, NewLoop} -> NewLoop(State)
end.
Предполагая, что NewLoop
является функцией более высокого порядка, всякий раз, когда вы отправляете сообщение {change, NewLoop}
процессу, изначально выполняющему функцию loop/1
, оно затем будет использовать NewLoop
в качестве определения.
Второй вариант - это тот, где вы хотите, чтобы процесс действовал как прокси (и изменял поведение). Это похоже на то, что предложил Марсело Кантос. Просто запустите цикл обработки и перешлите сообщения на новый (украдя его код):
become(Pid) ->
receive
Msg -> Pid ! Msg
end,
become(Pid).
Теоретически, это делает то, о чем просит бумага. На практике, однако, существуют риски при использовании второго варианта в реальной системе Erlang. При обмене данными между двумя процессами часто используется «печать» сообщения с идентификатором процесса отправителя, и что ответ будет помечен идентификатором процесса получателя. Можно выполнить обмен следующими сообщениями (это не код, а ручная запись):
A = <0.42.0> <-- process identifier
B = <0.54.0>,
A: {A, "hello"},
B: {B, "hi"},
A: {A, "how are you?"}.
B: {B, "Fine!"}.
Поэтому, когда A
ожидает сообщения от B
, он сможет сопоставить только их, используя такой шаблон, как {B, Message}
. В случае переадресованного сообщения эта схема адресации становится недействительной и просто нарушается.
Альтернативой может быть использование ссылок (make_ref()
) в качестве схемы адресации для сопоставления сообщений поверх возвращаемых Pids. Это будет разделять использование Pid в качестве адреса и идентификатора, используя два разных объекта.
Существует еще одна проблема, даже если адресация безопасна: зависимости процесса. Что происходит с именованными процессами, сбойными процессами, мониторами и т. Д.? Клиентские процессы могут иметь мониторы, ссылки и все такое, чтобы убедиться, что ничего не происходит без уведомления. Изменяя процесс маршрутизации, чтобы перехватывать сигналы выхода и пересылать их, можно сделать вещи более безопасными:
loop(State) ->
receive
{normal, Msg} -> loop(State);
{become, Pid} ->
process_flag(trap_exit, true), % catch exit signals as messages
link(Pid), % make it so if one process crashes, the other receives the signal
become(Pid)
end.
become(Pid) ->
receive
{'EXIT', Pid, killed} -> exit(self(), kill); %% uncatchable termination
{'EXIT', Pid, normal} -> ok; %% die normally too
{'EXIT', Pid, Reason} -> exit(Reason); %% die for the same reason as Pid
Msg -> Pid ! Msg %% forward the message
end,
become(Pid).
Этот проверенный код должен быть более безопасным, поскольку процессы, зависящие от первого процесса, получат те же сообщения об ошибках, что и сообщение, представленное Pid
в become(Pid)
, что делает маршрутизацию довольно прозрачной. Однако я не дал бы никаких гарантий, что это будет работать в долгосрочной перспективе с реальными приложениями.
Несмотря на то, что концептуально и достаточно просто представлять и делать такие вещи, как become
, стандартная библиотека Эрланга просто не была задумана с учетом второго варианта использования. Для реальных приложений я могу порекомендовать только первый метод, который широко используется каждым приложением Erlang, которое существует прямо сейчас. Второй необычный и может вызвать проблемы
** Вызовы функции become/1
в последнем примере, вероятно, должны быть ?MODULE:become(Pid)
, чтобы избежать возможных сбоев, связанных с горячей загрузкой кода в будущем. *