Модель агента приятно описана Агой Gul на его техническом отчете, "Агенты: модель параллельного вычисления в распределенных системах".
На странице 49 он объясняет команду, которой "становятся":
become <expression>
После вызова "становятся X", агент передаст все его сообщения к почтовому ящику (X) другого агента.
Я не уверен, однако, как это реализовано (это, это реализовано вообще) на языках как Erlang и Scala. Это - что-то, что я должен кодировать вручную? Что относительно эффективности? Ага показывает реализацию стека с помощью передачи сообщений. Каждый раз, когда поп или нажатие выполняются, еще одна передающая ссылка добавляется к некоторому агенту... После сотен тысяч операций я ожидал бы, что такая реализация потратит сообщения передачи слишком большого количества времени и не выполнение фактической работы, если некоторая хорошая оптимизация не была выполнена под капотом.
Таким образом, мой вопрос: как передает (или "станьте"), реализованный на типичных языках агента как Erlang, Scala (и библиотеки для других языков)?
См. Страницу 12 (страница 26 имеющейся у меня копии в формате PDF) статьи Аги «Действующие лица: модель параллельных вычислений в распределенной системе». "стать" - его актерский язык - это то, как вы указываете № 2, новое поведение для актера. Пересылка сообщений другому субъекту - лишь одно из многих возможных новых вариантов поведения.
Я думаю, что с актерами Scala вы, по сути, находитесь в той же лодке, что и с Erlang, если вам нужно поведение пересылки. Под капотом Scala "react" и "reactWithin" работают так же, как и стали, потому что частичная функция, определенная блоком response, является новым поведением актера, но я не уверен, что подобие даже намеренно.
Большинство (все?) Реализаций «актеров» довольно существенно отклоняются от модели акторов Хьюитта и актерского языка Аги. IIRC часть языка, определяющая поведение актеров на языке Аги, даже не является полной по Тьюрингу. Язык в целом становится полным по Тьюрингу только тогда, когда вы рассматриваете конфигурацию акторов. Я бы сказал, что отношения между моделью акторов и текущими структурами акторов подобны отношениям объектной ориентации в SmallTalk к объектной ориентации в C ++. Есть некоторая передача концепции и похожие термины, но в деталях они очень, очень разные.
Это напрямую не реализовано в Erlang, но вы можете написать тривиальную функцию стать
, которая получает сообщение, перенаправляет его другому процессу и затем вызывает себя:
become(Pid) ->
receive
Msg -> Pid ! Msg
end,
become(Pid).
(Промышленная версия
Вызов стать (Pid)
фактически превратит вызывающий процесс в процесс Pid
с точки зрения внешнего мира.
Это не решает проблемы, которые вы указали, поскольку повторные вызовы становятся
, вызывая рост цепочек переадресации. Однако в Erlang этого обычно не происходит, и я не уверен, как процессы Erlang отображаются на модель Actor.
Переход на Erlang здесь.
На базовом уровне доступны два варианта. Если вы хотите использовать стать
только для изменения поведения данного процесса (см. Пункт 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 ()
) в качестве схемы адресации для сопоставления сообщений поверх возвращаемых идентификаторов PID. Это разделит использование 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
в становится (Pid)
, что делает маршрутизацию довольно прозрачной. Однако я не стал бы давать никаких гарантий, что это будет работать в долгосрочной перспективе с реальными приложениями.
Несмотря на то, что это возможно и достаточно просто концептуально для представления и выполнения таких вещей, как превращается в
, стандартная библиотека Erlang просто не была продумана с учетом второго варианта использования.Для реальных приложений я могу порекомендовать только первый метод, который широко используется каждым приложением на Erlang, существующим прямо сейчас. Второй вариант встречается нечасто и может вызвать проблемы
* * Вызов функции становится / 1
в последнем примере, скорее всего, должен быть ? МОДУЛЬ: стать (Pid)
, чтобы избежать потенциальных сбоев, связанных с загрузкой горячего кода в будущем. *
Акторы Akka имеют концепцию "HotSwap", где вы можете послать новую PartialFunction актору, которая заменит его существующий обработчик сообщений. Предыдущий обработчик запоминается и может быть восстановлен. Подробности ищите по слову "hotswap" на http://doc.akkasource.org/actors.
Actor является контравариантным кофунктором, поэтому "become" - это просто comap.
Говоря иначе, Actor на сообщениях типа T - это, по сути, функция типа (T => Unit). А это просто композиция функций (с функцией тождества, возможно).
Это реализовано в Scalaz:
val a1 = actor(a => println(a))
val a2 = a1.comap(f)
Актор a2 применяет f к своим сообщениям и затем отправляет результат в a1.