Как актеры Эрланг отличаются от объектов ООП?

Предположим, что у меня есть актер Эрланга, подобный этому:

counter(Num) ->
  receive
    {From, increment} ->
      From ! {self(), new_value, Num + 1}
      counter(Num + 1);
  end.    

И аналогично, у меня есть класс Ruby, подобный этому:

class Counter
  def initialize(num)
    @num = num
  end

  def increment
    @num += 1
  end
end

Код Erlang написан в функциональном стиле, используя хвостовую рекурсию для поддержания состояния. Однако каково существенное влияние этой разницы? Для моих наивных глаз интерфейсы к этим двум вещам кажутся одинаковыми: вы отправляете сообщение, состояние обновляется, и вы возвращаете представление нового состояния.

Функциональное программирование так часто описывается как совершенно другая парадигма, чем ООП. Но актер Erlang, похоже, делает именно то, что должны делать объекты: поддерживать состояние, инкапсулировать и предоставлять интерфейс на основе сообщений.

Другими словами, когда я передаю сообщения между актерами Erlang, как это отличается от того, когда я передаю сообщения между объектами Ruby?

Я подозреваю, что для дихотомии функционального/ООП есть большие последствия, чем я вижу. Может ли кто-нибудь указать на них?

Отложите в сторону тот факт, что актер Erlang будет назначен VM и, таким образом, может работать одновременно с другим кодом. Я понимаю, что это большое различие между версиями Erlang и Ruby, но это не то, что я получаю. Concurrency возможен на других языках, включая Ruby. И хотя Erlang Concurrency может работать очень по-другому (иногда лучше), я не спрашиваю о различиях в производительности.

Скорее, меня больше интересует сторона функционального-vs-ООП вопроса.

Ответ 1

Другими словами, когда я передаю сообщения между актерами Erlang, как это отличается от того, когда я передаю сообщения между объектами Ruby?

Отличие состоит в том, что в традиционных языках, таких как Ruby, нет передачи сообщений, но вызов метода, который выполняется в одном потоке, и это может привести к проблемам с синхронизацией, если у вас многопоточное приложение. Все потоки имеют доступ к каждой другой памяти потоков.

В Erlang все актеры независимы, и единственный способ изменить состояние другого актера - отправить сообщение. Никакой процесс не имеет доступа к внутреннему состоянию любого другого процесса.

Ответ 2

IMHO это не лучший пример для FP против OOP. Различия обычно проявляются при доступе к/итерации и методах/функциях цепочки на объектах. Кроме того, возможно, понимание того, что такое "текущее состояние", работает лучше в FP.

Здесь вы ставите две разные технологии друг против друга. Один из них - F, другой OO.

Первое различие, которое я могу сразу заметить, - это изоляция памяти. Сообщения упорядочиваются в Erlang, поэтому легче избежать условий гонки.

Вторая часть - данные управления памятью. В Erlang обработка сообщений делится между отправителем и получателем. Существует два набора блокировок структуры процесса, которыми владеет Erlang VM. Поэтому, в то время как отправитель отправляет сообщение, он приобретает блокировку, которая не блокирует основные операции процесса (доступ через MAIN lock). Подводя итог, он дает Erlang более мягкую природу в реальном времени и полностью случайное поведение на стороне Ruby.

Ответ 3

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

Чтобы узнать, как работает FP, вы должны заглянуть внутрь актера и посмотреть, как он мутирует состояние. Ваш пример, когда состояние является целым числом, слишком прост. У меня нет времени, чтобы представить полный пример, но я нарисую код. Обычно петля актера выглядит следующим образом:

loop(State) ->
  Message = receive
  ...
  end,
  NewState = f(State, Message),
  loop(NewState).

Самое важное отличие от ООП заключается в том, что нет переменных мутаций, то есть NewState получается из состояния и может делиться с ним большинством данных, но переменная состояния всегда остается неизменной.

Это приятное свойство, так как мы никогда не испортили текущее состояние. Функция f обычно выполняет серию преобразований, чтобы превратить состояние в NewState. И только если/когда он полностью преуспеет, мы заменим старое состояние новым, вызвав цикл (NewState). Поэтому важным преимуществом является согласованность нашего состояния.

Второе преимущество, которое я нашел, - это более чистый код, но для этого требуется некоторое время. Как правило, поскольку вы не можете изменять переменную, вам придется разделить свой код на многие очень маленькие функции. Это действительно приятно, потому что ваш код будет хорошо учтен.

Наконец, поскольку вы не можете изменить переменную, легче рассуждать о коде. С изменчивыми объектами вы никогда не сможете быть уверены, будет ли изменена какая-либо часть вашего объекта, и она будет становиться все хуже, если использовать глобальные переменные. Вы не должны сталкиваться с такими проблемами при выполнении FP.

Чтобы попробовать, вы должны попытаться манипулировать некоторыми более сложными данными функциональным способом, используя чистые структуры erlang (не актеры, ets, mnesia или proc dict). Кроме того, вы можете попробовать его в ruby ​​с this

Ответ 4

Erlang включает в себя подход передачи сообщений Alan Kay OOP (Smalltalk) и функциональное программирование от Lisp.

То, что вы описали в своем примере, - это подход сообщения для ООП. Процессы отправки сообщений Erlang представляют собой концепцию, похожую на объекты Alan Kay, отправляющие сообщения. Кстати, вы можете получить эту концепцию, реализованную также в Scratch, где параллельные запущенные объекты отправляют сообщения между ними.

Функциональное программирование - это то, как вы кодируете процессы. Например, переменные в Erlang не могут быть изменены. Как только они будут установлены, вы сможете их прочитать. У вас также есть структура данных списка, которая работает в значительной степени подобно спискам Lisp, и у вас fun, которые привязаны к Lisp lambda.

Сообщение, переданное с одной стороны, и функционал с другой стороны - довольно разные вещи в Эрланге. При кодировании реальных приложений erlang вы тратите 98% своего времени на выполнение функционального программирования и 2% на мысль о передаче сообщений, что в основном используется для масштабируемости и concurrency. С другой стороны, когда вы сталкиваетесь с проблемой сложного программирования, вы, вероятно, будете использовать FP-сторону Erlang для реализации деталей альго и использовать передачу сообщений для масштабируемости, надежности и т.д.

Ответ 5

Что вы думаете об этом:

thing(0) ->
   exit(this_is_the_end);
thing(Val) when is_integer(Val) ->
    NewVal = receive
        {From,F,Arg} -> NV = F(Val,Arg),
                        From ! {self(), new_value, NV},
                        NV;
        _ -> Val div 2
    after 10000
        max(Val-1,0)
    end,
    thing(NewVal).

Когда вы создадите процесс, он будет жить самостоятельно, уменьшив его значение, пока не достигнет значения 0 и не отправит сообщение {'EXIT', this_is_the_end} в любой связанный с ним процесс, если вы не позаботитесь о выполнении чего-либо как:

ThingPid ! {self(),fun(X,_) -> X+1 end,[]}.
% which will increment the counter

или

ThingPid ! {self(),fun(X,X) -> 0; (X,_) -> X end,10}.
% which will do nothing, unless the internal value = 10 and in this case will go directly to 0 and exit

В этом случае вы можете видеть, что "объект" сам живет сам по себе параллельно с остальной частью приложения, что он может взаимодействовать с внешним миром почти без какого-либо кода и что внешняя сторона может попросить его сделать вещи, которые вы не знали при написании и компиляции кода.

Это глупый код, но есть какой-то принцип, который используется для реализации приложения, такого как транзакция mnesia, поведение... ИМХО понятие действительно другое, но вам нужно попытаться думать по-другому, если вы хотите его использовать правильно. Я уверен, что в Erlang можно написать код "OOPlike", но будет крайне сложно избежать concurrency: o), а в конце нет преимущества. Взгляните на принцип OTP, который дает некоторые треки о архитектуре приложения в Erlang (деревья наблюдения, пул "1 серверы одного клиента", связанные процессы, контролируемые процессы и, конечно, шаблон, сопоставляющий одно назначение, сообщения, кластер node...).