Обновить
103
Роман Смирнов@Source

Head of Elixir at Ecom.tech

0,6
Рейтинг
52
Подписчики
Отправить сообщение

Тут про полиморфизм речь шла, а не про наследование.

Нет, не значит. Иметь состояние — это нормально и для актора и для объекта. А вот что именно будет его состоянием зависит исключительно от роли, которую он выполняет.
Другими словами, не "данные и методы работы с ними", а "методы, реализующие определенную ответственность, и данные, необходимые для выполнения этой ответственности".


Звучит как будто разница не велика, но по факту разница огромна.
Первый подход: у нас есть деревянный брусок, давайте опишем всё (методы), что можно с ним сделать.
Второй подход: нам надо сделать скалку, давайте подумаем что (какие данные) нам для этого нужно.

Удобно структуры и методы для их обработки располагать в одном модуле

Лишь до тех пор, пока вы не начали писать хоть что-то нетривиальное. Когда одна и та же структура в разных контекстах обладает совершенно разным поведением, пихать все методы в один файл становится преступлением. В лучшем случае вы такие структуры продублируете аля Bounded Context, в худшем — получите классический God Object аля Active Record.
Если бы вы изначально проектировали от поведения, а не от структур, таких проблем бы даже не возникло. Но в этом случае вам бы даже в страшном сне не приснилось структуры и методы для их обработки надо располагать в одном модуле.


Иерархичное расположение структур позволяет сократить описание как структур, так и методов.

Сокращение описания структур — так себе цель. Во-первых, если у вас в структуре 30+ полей, а вы ещё от неё и наследуете, и полей добавляете — это уже плохо пахнет. Лучше подумать над доменной моделью, чем экономить десяток строк.

Например, через протоколы вы можете достигать полиморфизма даже от типов, к реализации которых не имеете доступа.
Пример: https://medium.com/everydayhero-engineering/extensibility-in-elixir-using-protocols-2e8fb0a35c48


Ещё один вариант: Type Classes в Haskell.


Одной инкапсуляции мало — вы правы.
ООП = обмен сообщениями + инкапсуляция + максимально позднее связывание.
А вот полиморфизм вообще к ООП никакого отношения не имеет.

Так в этом и дело, что когда объекты общаются посредством отсылки сообщений (как это в определении ООП было заложено), то у каждого объекта есть своя «очередь сообщений».
Проблема что по факту это реализовано только в модели акторов. А ООЯП на классах и вызовах методов изначально свернули не туда. В результате знаменитые холивары на темы типа "принтер печатает строку" vs "строка печатается на принтере".


Это не так просто, а часто и невозможно.

Я бы сказал, что это просто непривычно.


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

Это да. Но вам никто не мешает реализовать объект, получающий нужные данные из внешнего хранилища. Главное — не забыть, что никакой другой ответственности у него уже не должно быть, а преобразованием, обработкой, сохранением этих данных должны другие объекты заниматься.

Дело в том, что класс/объект никогда не должен строиться вокруг данных, он должен строиться вокруг поведения — одной определённой ответственности. Простейшее утверждение! При этом одно оно позволит избежать практически всех антипаттернов ООП. А большинство паттернов — просто следствия из него.


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

Эх, может соберусь когда-нибудь статью написать. В двух словах, основное дополнение — это метапрограммирование в стиле Lisp (манипуляции с AST). Многие другие плюсы (поддержка самого свежего Unicode, протоколы, примеси, etc.) являются как раз практическим применением этого метапрограммирования.

В Erlang (если я не прав, поправьте) обработка сообщения заключается в сопоставлении сообщения с образцом и выполнении соответствующего выражения.

Понимаете какая штука. С одной стороны, сказать так можно, но это слишком упрощенная трактовка. Как бы объяснить…


Представьте, что у вас несколько doesNotUnderstand и несколько способов отправить сообщение. В зависимости от способа отправки одно и то же сообщение попадёт в разные doesNotUnderstand. А внутри конкретного doesNotUnderstand оно — да — частенько будет сопоставляться с образцом (но не всегда). И опять же сопоставление с образцом и поиск метода по сигнатуре — это весьма разные вещи. Первое можно редуцировать до второго, но не наоборот.


Просто сойдемся на том, что Erlang ведь тоже еще не идеал?

В Erlang я пару субъективных минусов могу найти, а вот в Elixir до сих пор ни одного существенного минуса не нашёл. И даже сам в лёгком недоумении от этого. По-моему он объективно ближе всех к идеалу в современных реалиях. Не в 100% задач само собой. Я по бэкенду крупных веб-сервисов / мобильных приложений в основном специализируюсь, поэтому остальные сферы применения я просто не буду оценивать. Я мог бы рассказать чем кроме синтаксиса Elixir отличается от Erlang — но это будет слишком оффтопик.

Я надеюсь, за alankay1 на ycombinator скрывается именно ОН? :)

Да, это ОН. И на Quora тоже он. Я реально рекомендую Вам задать какие-то из ваших вопросы ему лично (мне кажется, у вас есть несколько, которые давно беспокоят). Он уже года 3 как достаточно активно отвечает на этих двух ресурсах на все вопросы, адресованные ему.
И именно по этим его сообщениям можно понять, что он не в восторге от текущих реализаций Smalltalk. Разумеется, он не может негодовать на эту тему открыто. Каким бы ни вырос твой ребёнок, он всё равно твой и ты его всё равно любишь. Но между строк сквозит некое сожаление и мощная ностальгия по первым реализациям.


Кстати, есть ещё достаточно интересное видео:
https://www.youtube.com/watch?v=fhOHn9TClXY


What's fun is that every Smalltalk contained the tools to make their successors while still running themselves

Да, а дальше про то, что реанимировать настоящие сообщения в Smalltalk — могло бы быть увлекательной задачей для опытных смалтолкеров. Впрочем, думаю, задачка достаточно нетривиальная.

выделили отдельный «концепт», более того, описали его на том же Smalltalk-е.

Ну смотрите, я по сути дал определение сообщения в Erlang:
Сообщение — данные, отправляемые адресату (актору) выбранным способом (обработка зависит от способа отправки, а не от пересылаемых данных).


Можете дать определение для Smalltalk? В принципе, наверно, стоило начать с определений, чтобы понять отличия.


этот самый обмен не обязателен для того, чтобы «настоящие» сообщения были: Erlang этот самый обмен на требует, но при этом имеет те самые «настоящие» сообщения.

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


Что касается внутреннего устройства акторов, это другой уровень — уровень реализации. Нет ничего страшного, что внутри клетки нет других клеток. Это позволяет прекрасно соблюдать SRP, Bounded Context и прочие важные архитектурные паттерны. У вас практически нет возможности напортачить со всем этим. В то же время, системы с обычным ООП повально страдают от несоблюдения этих принципов. И одна из основных причин почему так происходит — программист просто не понимает как правильно выделить классы. Вспоминается знаменитый холивар "строка печатает себя на принтере" vs "принтер печатает строку".


Понимание как правильно готовить ООП приходит с многолетним опытом, да и то не ко всем. А Erlang/Elixir с ходу даёт правильный подход из коробки. И уже набив шишки с популярным ООП, понимаешь насколько Армстронг был прав, говоря "любая отказоустойчивая архитектура в конце концов окажется похожей на Erlang".


Наделяя данные и структуры данных признаками объектов, мы совершали самую большую ошибку в применении ООП. Ужасно усложняя себе работу по сопровождению кода.


Объект никогда не должен строиться вокруг данных, он должен строиться вокруг поведения — определённой ответственности / должностной инструкции / etc. Простейшее утверждение! При этом одно оно позволит избежать практически всех антипаттернов ООП. А большинство паттернов — просто следствия из него.

Да, содержимое сообщения — это просто данные. Но чтобы его отправить нужно ещё выбрать способ отправки и получателя. Итого, данные, отправляемые адресату выбранным способом, — это сообщение. Пока данные никому не отправляются — это не сообщение. Этого достаточно чтобы выделить отдельный концепт.


Наконец, я же могу на Erlang-е и без акторов обойтись?

В том то и вся прелесть, что не можете. Всё есть акторы и ничего вне акторов не существует.

Можно ссылочку на негодование Кэя по этому поводу?

Есть цитата по поводу почему отказались от полноценных сообщений:


Take a look at the first implemented Smalltalk (-72). It implemented objects internally as a "receive the message" mechanism — a kind of quick parser — and didn't have dedicated selectors. (You can find "The Early History of Smalltalk" via Google to see more.)
This made the first Smalltalk "automatically extensible" in the dimensions of form, meaning, and pragmatics.

When Xerox didn't come through with a replacement for the Alto we (and others at Parc) had to optimize for the next phases, and this led to the compromise of Smalltalk-76 (and the succeeding Smalltalks). Dan Ingalls chose the most common patterns that had proved useful and made a fixed syntax that still allowed some extension via keywords. This also eliminated an ambiguity problem, and the whole thing on the same machine was about 180 times faster.

В общем, в Smalltalk когда-то были полноценные сообщения:


In Smalltalk-72, a message send was just a "notify" to the receiver that there was a message, plus a reference to the whole message. The receiver did the actual work of looking at it, interpreting it, etc.

Но их выпилили из-за performance-проблем, а терминология осталась. Отсюда и вся путаница.

Да не обязательно. Никто не заставляет как-то обрабатывать этот ответ. Этим сообщения и отличаются от вызова метода. Получатель может их игнорировать.


Ну, как веб-запрос вам возвращает статус, заголовки и тело ответа, но это вообще не значит, что любой клиент будет всё это как-то обрабатывать. Клиент может даже завершить свою работу до того, как сервер отправит ответ.

Почти в той же мере, в какой в Erlang-овских сообщениях зашита информация о том, какое выражение будет использовано для его обработки, нет?

Нет. Я могу знать, что какое-то сообщение попадёт в handle_call или в handle_cast, но от сообщения это никак не зависит. Обработчик зависит от того каким способом отправлено сообщение, а не от его содержания.


А может быть, есть шанс получить акторы без Erlang-овских заморочек?

Каких заморочек? Накостылись можно что угодно, но зачем? Когда есть проверенная десятилетиями реализация, которая отлично работает.


ограничиваясь «нормальными» объектами там в остальных случаях

Зачем нужны «нормальные» объекты? Если не нужен полноценный объект, ограничьтесь обычным значением или структурой.
Не знаю, как там Smalltalk обходит performance-проблемы от этой бесполезной упаковки простых данных в объекты. А бедные C#/Java/etc. с бесконечным boxing/unboxing только маются.

То есть встретить объект, состоящий из других объектов было бы тоже крайне странно. ;)

Угу, поэтому состояние объекта и не должно состоять из других объектов.


  1. Objects have their own memory, which consists of other objects.

Это формулировка Тима Бадда из книги "An Introduction to Object-Oriented Programming", которая со Smalltalk не особо связана.
А у Кея было так:


  1. Everything is an object.
  2. Objects communicate by sending and receiving messages (in terms of objects).
  3. Objects have their own memory (in terms of objects).

Причём он сам был недоволен, как Бадд переврал его определение, изменив смысл. Вот тут можно подробнее почитать, в том числе и про то, что версии Smalltalk до Smalltalk-80 базировались на модели акторов. В принципе, можно сказать, что Smalltalk-80 свернул на кривую дорожку и отказался от ООП в исходном понимании.


P.S. Собственно, чего тут спорить, Кей тут на днях сам написал, что
Erlang is much closer to the original ideas I had about “objects” and how to use them.
Можете с ним лично поспорить, если хотите :-)

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

Эх, я помню, как в 2005 году качал DRKB (Delphi Russian Knowledge Base) в CHM-формате, которую надо было ещё порезать на 3 части, чтобы на дискеты влезло. И это получилось провернуть только со второго раза. В первый раз отведённого часа в интернет-центре не хватило, чтобы скачать эти 3.5 Mb

Дело в том, что «Erlang-овский ООП» не предполагает никакого ООП на уровне «ниже процессов»

С одной стороны — да, с другой — это всё-таки не уровень ОС, это легковесные процессы виртуальной машины. И их может быть много тысяч на одной машине.


А по поводу внутреннего устройства, так это и к клеткам применимо. Даже если любой организм состоит из клеток, то внутри клетки очевидно не другие клетки, а митохондрии, рибосомы и т.д. Другими словами, встретить объект внутри объекта было бы крайне странно.
Поэтому я бы сказал, что пункт «все есть объект» в Erlang тоже выполняется, т.к. нет никакой возможности выполнить какой-то код вне актора. А пункта «объект должен состоять из других объектов» вроде никогда не было.

На следующем митапе можем обсудить )

В Smalltalk-е тоже прием сообщения и код реакции не связаны напрямую!

Вообще связаны, в штатном для Smalltalk случае, в самом "сообщении" зашита информация какой метод будет вызван для его обработки.


Да, если вы будете принимать все сообщения через doesNotUnderstand, потом их как-то асинхронно обрабатывать, и возвращать результат через thisContext — то есть шанс получить кривую и не очень стабильную реализацию Erlang поверх Smalltalk. Весь вопрос в том, что это будет неестественно для Smalltalk. Настоящие сообщения неестественны для Smalltalk, но да — теоретически их можно эмулировать, правда, для этого ещё придётся полностью переписать стандартную библиотеку.


сообщение не обязательно связывается с выполняемым кодом по имени метода

Вы "не" не туда поставили. Должно быть "обязательно не связывается".

Информация

В рейтинге
2 347-й
Откуда
Россия
Работает в
Зарегистрирован
Активность