Спасибо за столь развернутый комментарий. По поводу синхронности-асинхронности соглашусь. По поводу распределенности «из коробки» нужно будет подготовить более обстоятельный ответ. Поэтому пока выскажусь вот по этому поводу:
Часто задаюсь вопросом: почему люди, что-то сделавшие, другим советуют этого не делать? Вполне возможно, у другого человека/команды ход развития событий будет иной…
Дело в том, что когда ты что-то делаешь сам, ты лучше понимаешь, насколько мало приходится заниматься тем, чем хочется и как много времени и сил отнимает рутина. При разработке своего фреймворка/блиблиотеки хорошо, если хотя бы 20% времени уходит именно на разработку. Гораздо больше тратиться на отладку, баг-фиксинг, тестирование, подготовку примеров, документации, вывод всего этого дела во внешний мир, на публику. Тут, на Хабре, недавно было интересное интервью. Там хорошо рассказано о том, во что реально обходится вывод чего-то в OpenSource.
Даже если инструмент не выводится на публику, а остается внутренним инструментом компании, в которой он был разработан, то ситуация принципиально не облегчается. Может быть даже, напротив, становится хуже. Ведь внутренний продукт вряд ли будет настолько же хорошо задокументирован (хотя бы задокументирован). Следовательно, значительную часть времени разработчик инструмента будет тратить на обучение тех, кто инструментом вынужден пользоваться. А если документация будет как обычно для внутренних разработок, т.е. почти никак, то и сам разработчик с годами будет тратить все больше и больше времени на то, чтобы разобраться в своем же старом коде. Значительная часть времени будет уходить на поиск проблем с фреймворком. Причем даже не самого фреймворка, сколько проблем в коде тех, кто его использует. А использовать правильно сложно, т.к. документация оставляет желать… Да и сам фреймворк, будучи внутренним продуктом, не сможет одинаково хорошо закрывать все хотелки даже внутренних пользователей. Поэтому его иногда будут использовать неправильно, это будет приводить к проблемам, разбирательство с которыми будет отнимать время и силы.
Кроме того, не нужно думать, что можно создать фреймворк, который будет нравиться всем. Не думаю, что это в принципе возможно, ведь фреймворк — это не 100-долларавая купюра :) Так что среди внешней публики обязательно найдется некоторое количество активных горлопанов критиков, которые будут объяснять и вам, и окружающим, что то, что вы сделали — это говно, оно не работает, оно хуже, чем что-то другое, оно вообще в принципе не нужно, поскольку такие задачки нормальные программисты сами решают на коленке за 15 минут.
В общем, кроме собственно вынашивания идей для фреймворка и их реализации, есть еще куча говна всего, чем приходится заниматься. Пока сам в эту кучу не вляпаешься, то и не подозреваешь, насколько она огромная и вонючая ;)
Вам спасибо!
Есть планы продолжать серию. Так что если есть желание узнать что-то конкретное или нужно что-то прояснить, или рассказать подробнее, то обозначьте что именно. И нам будет проще при подготовке следующих статей. И вам, читателям, будет интереснее.
По поводу SEDA, исхожу только из вашего описания — реализовать задачу как последовательность событий/шагов, каждый из которых выполняется асинхронно можно без проблем и не используя акторы.
Вы правы, можно. Подход SEDA был упомянут для того, чтобы обозначить альтернативу варианту, когда акторы создаются на каждый чих. При этом SEDA-подход (если мы не привязываемся к оригинальному фреймворку SEDA) не говорит о том, как именно реализованы стадии. Это могут быть просто рабочие нити (или короутины), связанные каналами (скажем, в языке Go это естественным образом ложиться на Go-шные каналы и goroutines). Могут быть и акторы, как в нашем случае. Может быть еще что-то. Имхо, с акторами все-таки удобнее, чем с голыми нитями.
Но вы не рассказали самое главное это хранение состояния в акторе, и создание актора на каждую сущность в системе — пользователя, обработчика событий(может быть много акторов) и т.д., хотя я тут не совсем корректен вы об этом мельком сказали и тут же написали что так делать не нужно.
Про то, что акторы удобны для хранения изолированных состояний я пытаюсь говорить на протяжении уже ряда статей. Другое дело, что не всегда выгодно оформлять состояние отдельной сущности именно в виде актора. Тот же самый пользователь, который залогинился в систему, может быть представлен отдельным актором. А может быть всего одной из записей в одном единственном акторе, который следит за всеми залогиненными пользователями.
В зависимости от задачи можно предпочесть один подход другому. Моя цель была показать, что если не рассматривать альтернативы и тупо использовать возможности фреймворка по созданию большого количества акторов, то можно столкнуться, как минимум, с двумя неприятными проблемами. Можно и не столкнуться. Но знать об этом заранее нужно. Мы вот, например, изначально об этом не знали, набили шишек. Теперь относимся к выбору между этими вариантами очень серьезно.
Почему то негативно отозвались о прозрачном распределенном взаимодействии которое должна предоставлять система акторов
Не совсем так. Речь шла о том, почему мы со временем отказались от поддержки таких возможностей «из коробки». И одна из основных причин была как раз в том, что для устранения перечисленных в статье проблем со встроенной распределенностью требуется очень приличное количество трудозатрат.
Ни чего не сказали о подходе fault-tolerance, хотя это скорее специфика конкретной реализации а не модели акторов, хотя это и вытекает из модели акторов.
Эта тема затрагивалась в предыдущей статье (раздел «Коды ошибок vs Исключения»). Если этого недостаточно, то скажите, каких именно подробностей вам не хватило, постараюсь раскрыть тему.
Например, продемонстрированное ранее отсутствие кругозора, категоричность и максимализм. А так же откровенные заблуждения.
Я расписал все: есть язык Х (не обязательно плюсы, абсолютно любой), если есть язык У который ничем не хуже и позволяет прозрачно переиспользовать функционал Х, то смысла писать на Х нет никакого.
Вы забываете о существующих наработках и разработках. Если для языка X есть мегабиблиотека, которая позволяет за пару рабочих дней (а то и часов) собрать решение под какую-то типовую задачу с немного различающимися условиями, то языком X будут продолжать пользоваться даже после того, как аналог появится на языке Y. Хотя появление этого самого аналога — это еще большой вопрос.
Например, появление хороших вычислительных библиотек на C++ отнюдь не мешает продолжать использовать Fortran на серьезных вычислительных задачах. А наличие для C++ того же Eigen-а отдаляет пришествие Rust-а в эту нишу на неопределенное время.
C++ мог бы использоваться в разработке ОС даже на самом низком уровне, т.к. позволяет практически тоже самое, что и С. Тем не менее, ядра Linux и FreeBSD как разрабатывались на C, так и продолжат разрабатываться. И заменить C, который еще более древний и убогий, чем C++, Rust-у здесь так просто не получится.
Изначально речь шла вообще о другом, а именно о том, что и шарпы, и остальные перечисленные языки намного проще в изучении, чем плюсы.
Так а какой практический вывод из этого? Бросить заниматься C++ и уйти всем на более молодые языки, при проектировании которых использовался опыт того же C++?
Ваша проблема в том, что вы считаете такой уход не просто разумным, но и неизбежным. А для многих текущих C++ проектов это совсем не так.
Жизнеспособность, востребованность, используемость и, уж тем более, популярность — это все слабосвязанные друг с другом вещи. Ada вполне себе живой язык, востребованный и используемый, развивающийся (последняя версия стандарта от 2012-го года), с регулярно обновляющимися инструментами для разработки. То, что это не попадает в поле вашего зрения, не меняет объективного положения дел.
У вас и по поводу C++ какое-то избирательное суждение: чего не видите, того не существует, что не популярно, то не жизнеспособно. Отсюда и выводы о том, насколько хреново все в C++ и что поэтому C++ники должны искать выход в Rust-е (как до этого в C#, а еще до этого в Java, а еще до этого в Delphi, а еще до этого в Eiffel, ...). Остается только порадоваться, что C++ развивается не смотря на мнение таких судильщиков. Ибо вы явно даже отдаленно не представляете себе, сколько кода на C++ уже написано, как долго он будет использоваться и развиваться, и сколько еще на C++ напишут до того, как тот же Rust перейдет в категорию действительно широко используемого инструмента.
Мне кажется, вы в очередной раз ошибаетесь. Если для вас «очевидный ответ» — это ООП, то C++ позаимствовал ООП из Simula, а не из Smalltalk. Тут можно еще много чего наговорить про различия между ООП для статически типизированных ЯП и для динамически типизированных, а так же о том, почему Smalltalk имеет совсем косвенное отношение к ООП для статически-типизированных языков, а так же почему Smalltalk считают «родителем» ООП не смотря на то, что та же Simula появилась гораздо раньше. Но это здесь явно офтоп.
Ну, так можно хоть аду считать живым ЯП.
Ладно бы вы COBOL отказывались признавать живым. Но назвать Ada мертворожденной… Это сильно. От человека с таким кругозором критика C++ начинает играть новыми красками.
Можно долго рассуждать на тему, что в Smalltalk'е это было за миллион лет до плюсов, остальные идеи были взяты тоже откуда-то еще, но скомпоновалось это весьма удачно именно в плюсах.
Вы прям заинтриговали. Что именно из C++ было в Smalltalk-е задолго до того как? А то я, например, все еще нахожусь под впечатлением, что C++ позаимствовал многое из Simula, ML и Ada, но отнюдь не из Smalltalk.
Смотря что мы имеем ввиду под «использованием».
Наличие business-critical приложений, прекращение работы которых станет заметным для большого количества людей. Которые нуждаются в поддержке и развитии, и которые не могут по каким-то причинам быть переписаны на более современных языках программирования.
По крайней мере про кобол мне страшно даже думать, что кто-то на нем еще пишет.
А вы наберитесь смелости, представьте себе такое. Может категоричности по отношению к тому же C++ станет поменьше.
Ну так применительно к статье про новый стандарт C++ и про предложения для следующего стандарта кроме констатации «все пропало» хотелось бы услышать какие-то идеи «по спасению». Если идея в том, что «пора валить» на Rust, ну OK.
Просто сейчас С++ из локомотива инноваций превратился в догоняющий язык
Просто интересно, когда это C++ был «локомотивом инноваций»?
новый язык становится настолько хорош, что люди пересиливают свою инертность и начинают миграцию. Так было с С, так было с С++, так было с джавой/коболом/...
Боюсь вас огорчить, но и C, и C++, и Java и даже COBOL продолжают активнейшим образом использоваться.
Так а никто и не сказал, что ваш диагноз верен и что вы, действительно, правы ;)
С точки зрения С++-программистов выход есть и еще какой — тот же Rust это и есть C++
Это, может быть, выход для «С++ программистов», но совсем не выход для C++ проектов. Переписывать мегатонны готового и работающего C++ кода на Rust или какой-то другой язык просто так никто не будет. Соответственно, все упирается в то, как развивать C++, чтобы дописывать новый код на C++ и модернизировать старый код было проще, дешевле и безопаснее.
С++ сильно продвинулся в этом направлении. Даже если брать более короткий временной интервал после 2003-го года, то в языке появилось больше средств для безопасной работы:
* auto для вывода типа переменных (устраняют ошибки вроде int l = strlen(s));
* range-for (нет целого класса ошибок с выходом за рамки массивов из-за плюс-минус единички);
* лямбды (делают удобным использование стандартных алгоритмов вместо ручных циклов, которые были источниками ошибок);
* variadic templates (сильно уменьшают надобность в макросах);
* rvalue references и move-semantic (сильно упрощают, например, прием и возврат «тяжелых» значений);
* std::array вместо C-шных массивов;
* std::unique_ptr (исправляет косяки std::auto_ptr);
* auto для вывода типа возвращаемых значений и типов в полиморфных лямбдах (опять же уменьшают надобность в макросах).
Если плотно сидишь на C++11/14, а потом внезапно возвращаещься в рамки C++98/03, то очень быстро выясняешь, насколько сильно упростился язык в повседневном применении.
Ну и еще один фактор, по поводу:
Это я и написал, сложность плюсов в том, что под этим названием скрывается целый класс мета-языков, и код одного «плюсовика» будет совершенно непонятен другому.
когда на C++ пишут софт для микроконтроллеров, драйвера и куски ядер ОС, бортовое ПО, СУБД, сложные десктоп-приложения, кросс-платформенные мобильные приложения (целиком или частично), компиляторы и виртуальные машины для языков программирования, игры, ресурсоемкие расчеты и т.д., то вполне естественно, что в каждой из этих ниш используется свое подмножество C++. Так что тот факт, что C++ очень разный — это еще один фактор, который влияет на то, как быстро и в какую сторону (в какие стороны) язык развивается.
Есть, что наводит на мысль о том, что [[implies(std::pure)]] вполне себе реализуемо. Осталось чтобы:
a) можно было выставлять требования для блоков кода (например, [[expects(std::pure)]]) с соответствующей диагностикой от компилятора в случае нарушения;
b) включения этого в стандарт, чтобы это было не только в одном конкретно взятом компиляторе.
Ну мы же делать еще ничего не побежали. А вот в перспективный wish list почему бы и не добавить. Тем более, что наш опыт ограничен и SObjectizer, насколько мне известно, не использовался в проектах размером более 1MLOC. Так что может на других масштабах эта фича будет более востребованной.
Тем не менее, про примеры. Примеры с рефакторингом хороши, даже не смотря на то, что мы сами по каким-то причинам с подобными проблемами не сталкивались, представить себе их актуальность несложно.
А вот за примерам того, что агент-обработчик заинтересован в знании типа агента-отправителя, приходится идти куда-то далеко… :(
Вы задаете вопросы, которые отлично разобраны в книге «Дизайн и эволюция языка C++». Пытаться отвечать вам здесь — это как пытаться пересказать изрядную часть этой книги своими словами.
Можете прочитать всю ветку комментариев еще раз, все мои доводы там перечислены.
Уверяю вас, все доводы прочитаны и осмыслены. Более того, я сам сторонник статической типизации и для проектов свыше 5KLOC вряд ли выберу динамически-типизированный язык.
Я продолжаю выпытывать у вас подробность из-за того, что отвечаю за разработку акторного фреймворка и пытаюсь понять, можно или из того, что вы рассказываете получить аргументы «за» для добавления в фреймворк дополнительной функциональности.
Поэтому и интересны примеры из жизни, дабы идею поддержки более строгой статической типизации было проще «продать» остальной команде.
там ведь тоже объекты вынуждены приспосабливаться к интерфейсам других объектов, посредством вызова методов друг друга
Не совсем так. Если есть что-то вроде:
class connection_pool {
public:
optional<connection> acquire();
...
};
то нам не нужно заботиться о том, какой интерфейс имеет объект, который дергает connection_pool::acquire. Аналогичная ситуация и с акторами: есть сообщение acquire и есть ответное сообщение take. С ними все понятно. Легко придумать пример, который демонстрирует их надобность.
А вот когда заходит речь о том, что для отсылки ответного сообщения take нужно, чтобы отправитель имел какой-то специальный тип take_acceptor, то здесь уже возникают проблемы с придумыванием конкретных примеров из практики, которые могли бы это все оправдать.
В общем, эти все вопросы из разряда PHP vs Java. В PHP можно сделать $anyVar->anyMethod(), и получить ошибку на этапе исполнения
Не совсем так. Принципиальная разница в том, что если в динамически-типизированном языке вы вызываете у объекта метод, которого у объекта нет, то вы гарантированно получите ошибку в run-time. В случае же с акторами, если вы отправляете актору сообщение, которое актор не поддерживает, вы об этом не узнаете. Более того, если вы отсылаете актору сообщение, которое он поддерживает, но сообщение до получателя не доходит, то вы имеете точно такой же эффект. Т.е. вы якобы получаете гарантии в компайл-тайм, которые ничего не стоят в ран-тайм.
И еще один момент. Если нетипизированные actor_reference настолько мешают жить, то ведь очень легко они превращаются в типизированных. Достаточно сделать что-то вроде:
В данном случае, A1 и A2 — это всего лишь интерфейсы, которые говорят о том, что эти акторы могут принимать сообщения M1 и M2 соответсвтенно.
Ну так в Модели Акторов M1 и M2 и есть те самые интерфейсы A1 и A2. Просто в статически типизированных ОО языках мы привыкли, что интерфейсы выражаются через перечисление методов. А в Модели Акторов интерфейсы выражаются через перечисление сообщений.
Типобезопасность не является самоцелью, как я надеюсь. Akka, как и SObjectizer, — это инструмент для решения практических задач. Если типобезопасность в каких-то задачах становится настолько важной, что этому уделяется внимание, то должны быть какие-то примеры.
Пока же разговор идет о том, что раз мы находимся в рамках статической типизации, то хотим иметь ее везде. В том числе и при взаимодействии акторов посредством асинхронных сообщений. Только вот проблема в том, что преимущества статической типизации для синхронных вызовов — они понятны и очевидны. А вот типизация асинхронного обмена сообщениями… Не понятно, и не очевидно.
Протокол ограничен типами с двух сторон, то есть, актор A1, получая сообщение типа M1, уверен на этапе компиляции, что его ему отправил актор A2, которому можно ответить сообщением M2.
Но зачем это? Это все может решаться и без попыток подружить Модель Акторов со статической типизацией.
Сильно зависит от того, как обучать новичков C++. Если людей учить сначала чистому C, а потом с C переходить на C++, то вы правы, кривая обучения получается очень крутой.
Если же сразу учить C++, без отсылок на C, то все гораздо проще. Когда людей учат сразу использовать std::array или std::vector вместо старых массивов, когда range-for или std::for_each+лямбда используются вместо старых for(i=0;i<ASIZE(a);++i), когда из функции возвращается std::pair<bool,std::string> вместо возврата отдельно bool-а и отдельно вручную аллоцированной строки через out-параметр и т.д., и т.п., то современный C++ осваивается гораздо легче, чем тот же C++98/03.
На этапе компиляции я не вижу возможности эту проблему как-то решить.
А зачем ее решать на этапе компиляции?
Тут скорее вопрос владения ссылкой на актора-получателя. Если ссылкой владеет только один отправитель, то никаких сложностей нет, идет обычное взаимодействие 1:1. Если ссылкой владеет N отправителей, то ничто не препятствует взаимодействию N:1. Если конкретного программиста N:1 для конкретного актора-получателя не устраивает то… То такому программисту нужно выдумывать для своей задачи какой-то аналог Rust-овских «заимствований» для actor_reference. Чтобы добиться гарантий того, что в конкретный момент времени actor_reference доступен только одному отправителю.
Только есть большие сомнения в том, что многим потребуется что-то подобное.
Гарантировать тип отправителя можно, например, в том случае, если «зашить» его в протокол, то есть ограничить интерфейс с двух сторон: что вот это сообщение может приходить только от такого типа акторов, и только такому типу акторов.
А зачем это нужно? В каких практических задачах это может потребоваться?
Я еще могу понять, когда на уровне контракта пытаются описать, что в ответ на сообщение X может быть получено Y или Z. Но чтобы при этом требовалось, что ответ на X должен получить только актор типа N, это уже что-то противоречащее здравому смыслу. Ибо такая типизация мешает переиспользованию акторов.
Какой-нибудь актор connection_pool сегодня используется в одном проекте, где он общается с акторами temperature_sensors, а завтра будет использоваться в другом проекте, где он общается с акторами risk_managers. Введение требований к типам агентов-consumer-ов приведет к тому, что эти требования придется удовлетворять и для temperature_sensors, и для risk_managers. Ну и зачем это нужно, если temperature_sensors и risk_managers и так будут вынуждены приспосабливаться к интерфейсу connection_pool-а на уровне сообщений acquire, release, take и failure?
Дело в том, что когда ты что-то делаешь сам, ты лучше понимаешь, насколько мало приходится заниматься тем, чем хочется и как много времени и сил отнимает рутина. При разработке своего фреймворка/блиблиотеки хорошо, если хотя бы 20% времени уходит именно на разработку. Гораздо больше тратиться на отладку, баг-фиксинг, тестирование, подготовку примеров, документации, вывод всего этого дела во внешний мир, на публику. Тут, на Хабре, недавно было интересное интервью. Там хорошо рассказано о том, во что реально обходится вывод чего-то в OpenSource.
Даже если инструмент не выводится на публику, а остается внутренним инструментом компании, в которой он был разработан, то ситуация принципиально не облегчается. Может быть даже, напротив, становится хуже. Ведь внутренний продукт вряд ли будет настолько же хорошо задокументирован (хотя бы задокументирован). Следовательно, значительную часть времени разработчик инструмента будет тратить на обучение тех, кто инструментом вынужден пользоваться. А если документация будет как обычно для внутренних разработок, т.е. почти никак, то и сам разработчик с годами будет тратить все больше и больше времени на то, чтобы разобраться в своем же старом коде. Значительная часть времени будет уходить на поиск проблем с фреймворком. Причем даже не самого фреймворка, сколько проблем в коде тех, кто его использует. А использовать правильно сложно, т.к. документация оставляет желать… Да и сам фреймворк, будучи внутренним продуктом, не сможет одинаково хорошо закрывать все хотелки даже внутренних пользователей. Поэтому его иногда будут использовать неправильно, это будет приводить к проблемам, разбирательство с которыми будет отнимать время и силы.
Кроме того, не нужно думать, что можно создать фреймворк, который будет нравиться всем. Не думаю, что это в принципе возможно, ведь фреймворк — это не 100-долларавая купюра :) Так что среди внешней публики обязательно найдется некоторое количество активных
горлопановкритиков, которые будут объяснять и вам, и окружающим, что то, что вы сделали — это говно, оно не работает, оно хуже, чем что-то другое, оно вообще в принципе не нужно, поскольку такие задачки нормальные программисты сами решают на коленке за 15 минут.В общем, кроме собственно вынашивания идей для фреймворка и их реализации, есть еще куча
говнавсего, чем приходится заниматься. Пока сам в эту кучу не вляпаешься, то и не подозреваешь, насколько она огромнаяи вонючая;)Есть планы продолжать серию. Так что если есть желание узнать что-то конкретное или нужно что-то прояснить, или рассказать подробнее, то обозначьте что именно. И нам будет проще при подготовке следующих статей. И вам, читателям, будет интереснее.
Про то, что акторы удобны для хранения изолированных состояний я пытаюсь говорить на протяжении уже ряда статей. Другое дело, что не всегда выгодно оформлять состояние отдельной сущности именно в виде актора. Тот же самый пользователь, который залогинился в систему, может быть представлен отдельным актором. А может быть всего одной из записей в одном единственном акторе, который следит за всеми залогиненными пользователями.
В зависимости от задачи можно предпочесть один подход другому. Моя цель была показать, что если не рассматривать альтернативы и тупо использовать возможности фреймворка по созданию большого количества акторов, то можно столкнуться, как минимум, с двумя неприятными проблемами. Можно и не столкнуться. Но знать об этом заранее нужно. Мы вот, например, изначально об этом не знали, набили шишек. Теперь относимся к выбору между этими вариантами очень серьезно.
Не совсем так. Речь шла о том, почему мы со временем отказались от поддержки таких возможностей «из коробки». И одна из основных причин была как раз в том, что для устранения перечисленных в статье проблем со встроенной распределенностью требуется очень приличное количество трудозатрат.
Эта тема затрагивалась в предыдущей статье (раздел «Коды ошибок vs Исключения»). Если этого недостаточно, то скажите, каких именно подробностей вам не хватило, постараюсь раскрыть тему.
Например, продемонстрированное ранее отсутствие кругозора, категоричность и максимализм. А так же откровенные заблуждения.
Вы забываете о существующих наработках и разработках. Если для языка X есть мегабиблиотека, которая позволяет за пару рабочих дней (а то и часов) собрать решение под какую-то типовую задачу с немного различающимися условиями, то языком X будут продолжать пользоваться даже после того, как аналог появится на языке Y. Хотя появление этого самого аналога — это еще большой вопрос.
Например, появление хороших вычислительных библиотек на C++ отнюдь не мешает продолжать использовать Fortran на серьезных вычислительных задачах. А наличие для C++ того же Eigen-а отдаляет пришествие Rust-а в эту нишу на неопределенное время.
C++ мог бы использоваться в разработке ОС даже на самом низком уровне, т.к. позволяет практически тоже самое, что и С. Тем не менее, ядра Linux и FreeBSD как разрабатывались на C, так и продолжат разрабатываться. И заменить C, который еще более древний и убогий, чем C++, Rust-у здесь так просто не получится.
Так а какой практический вывод из этого? Бросить заниматься C++ и уйти всем на более молодые языки, при проектировании которых использовался опыт того же C++?
Ваша проблема в том, что вы считаете такой уход не просто разумным, но и неизбежным. А для многих текущих C++ проектов это совсем не так.
При таком уровне аргументации остается только спросить: сколько вам лет?
Но это так, риторический вопрос, вы уже достаточно рассказали для того, чтобы понять, как относиться к вашему мнению.
У вас и по поводу C++ какое-то избирательное суждение: чего не видите, того не существует, что не популярно, то не жизнеспособно. Отсюда и выводы о том, насколько хреново все в C++ и что поэтому C++ники должны искать выход в Rust-е (как до этого в C#, а еще до этого в Java, а еще до этого в Delphi, а еще до этого в Eiffel, ...). Остается только порадоваться, что C++ развивается не смотря на мнение таких судильщиков. Ибо вы явно даже отдаленно не представляете себе, сколько кода на C++ уже написано, как долго он будет использоваться и развиваться, и сколько еще на C++ напишут до того, как тот же Rust перейдет в категорию действительно широко используемого инструмента.
Мне кажется, вы в очередной раз ошибаетесь. Если для вас «очевидный ответ» — это ООП, то C++ позаимствовал ООП из Simula, а не из Smalltalk. Тут можно еще много чего наговорить про различия между ООП для статически типизированных ЯП и для динамически типизированных, а так же о том, почему Smalltalk имеет совсем косвенное отношение к ООП для статически-типизированных языков, а так же почему Smalltalk считают «родителем» ООП не смотря на то, что та же Simula появилась гораздо раньше. Но это здесь явно офтоп.
Ладно бы вы COBOL отказывались признавать живым. Но назвать Ada мертворожденной… Это сильно. От человека с таким кругозором критика C++ начинает играть новыми красками.
Наличие business-critical приложений, прекращение работы которых станет заметным для большого количества людей. Которые нуждаются в поддержке и развитии, и которые не могут по каким-то причинам быть переписаны на более современных языках программирования.
А вы наберитесь смелости, представьте себе такое. Может категоричности по отношению к тому же C++ станет поменьше.
Просто интересно, когда это C++ был «локомотивом инноваций»?
Боюсь вас огорчить, но и C, и C++, и Java и даже COBOL продолжают активнейшим образом использоваться.
Так а никто и не сказал, что ваш диагноз верен и что вы, действительно, правы ;)
Это, может быть, выход для «С++ программистов», но совсем не выход для C++ проектов. Переписывать мегатонны готового и работающего C++ кода на Rust или какой-то другой язык просто так никто не будет. Соответственно, все упирается в то, как развивать C++, чтобы дописывать новый код на C++ и модернизировать старый код было проще, дешевле и безопаснее.
С++ сильно продвинулся в этом направлении. Даже если брать более короткий временной интервал после 2003-го года, то в языке появилось больше средств для безопасной работы:
* auto для вывода типа переменных (устраняют ошибки вроде int l = strlen(s));
* range-for (нет целого класса ошибок с выходом за рамки массивов из-за плюс-минус единички);
* лямбды (делают удобным использование стандартных алгоритмов вместо ручных циклов, которые были источниками ошибок);
* variadic templates (сильно уменьшают надобность в макросах);
* rvalue references и move-semantic (сильно упрощают, например, прием и возврат «тяжелых» значений);
* std::array вместо C-шных массивов;
* std::unique_ptr (исправляет косяки std::auto_ptr);
* auto для вывода типа возвращаемых значений и типов в полиморфных лямбдах (опять же уменьшают надобность в макросах).
Если плотно сидишь на C++11/14, а потом внезапно возвращаещься в рамки C++98/03, то очень быстро выясняешь, насколько сильно упростился язык в повседневном применении.
Ну и еще один фактор, по поводу:
когда на C++ пишут софт для микроконтроллеров, драйвера и куски ядер ОС, бортовое ПО, СУБД, сложные десктоп-приложения, кросс-платформенные мобильные приложения (целиком или частично), компиляторы и виртуальные машины для языков программирования, игры, ресурсоемкие расчеты и т.д., то вполне естественно, что в каждой из этих ниш используется свое подмножество C++. Так что тот факт, что C++ очень разный — это еще один фактор, который влияет на то, как быстро и в какую сторону (в какие стороны) язык развивается.
a) можно было выставлять требования для блоков кода (например, [[expects(std::pure)]]) с соответствующей диагностикой от компилятора в случае нарушения;
b) включения этого в стандарт, чтобы это было не только в одном конкретно взятом компиляторе.
Тем не менее, про примеры. Примеры с рефакторингом хороши, даже не смотря на то, что мы сами по каким-то причинам с подобными проблемами не сталкивались, представить себе их актуальность несложно.
А вот за примерам того, что агент-обработчик заинтересован в знании типа агента-отправителя, приходится идти куда-то далеко… :(
Уверяю вас, все доводы прочитаны и осмыслены. Более того, я сам сторонник статической типизации и для проектов свыше 5KLOC вряд ли выберу динамически-типизированный язык.
Я продолжаю выпытывать у вас подробность из-за того, что отвечаю за разработку акторного фреймворка и пытаюсь понять, можно или из того, что вы рассказываете получить аргументы «за» для добавления в фреймворк дополнительной функциональности.
Поэтому и интересны примеры из жизни, дабы идею поддержки более строгой статической типизации было проще «продать» остальной команде.
Не совсем так. Если есть что-то вроде:
то нам не нужно заботиться о том, какой интерфейс имеет объект, который дергает connection_pool::acquire. Аналогичная ситуация и с акторами: есть сообщение acquire и есть ответное сообщение take. С ними все понятно. Легко придумать пример, который демонстрирует их надобность.
А вот когда заходит речь о том, что для отсылки ответного сообщения take нужно, чтобы отправитель имел какой-то специальный тип take_acceptor, то здесь уже возникают проблемы с придумыванием конкретных примеров из практики, которые могли бы это все оправдать.
Не совсем так. Принципиальная разница в том, что если в динамически-типизированном языке вы вызываете у объекта метод, которого у объекта нет, то вы гарантированно получите ошибку в run-time. В случае же с акторами, если вы отправляете актору сообщение, которое актор не поддерживает, вы об этом не узнаете. Более того, если вы отсылаете актору сообщение, которое он поддерживает, но сообщение до получателя не доходит, то вы имеете точно такой же эффект. Т.е. вы якобы получаете гарантии в компайл-тайм, которые ничего не стоят в ран-тайм.
И еще один момент. Если нетипизированные actor_reference настолько мешают жить, то ведь очень легко они превращаются в типизированных. Достаточно сделать что-то вроде:
И дальше у вас будет типизированная ссылка на актора, по которой вы легко определите, какой интерфейс есть у этого актора.
Ну так в Модели Акторов M1 и M2 и есть те самые интерфейсы A1 и A2. Просто в статически типизированных ОО языках мы привыкли, что интерфейсы выражаются через перечисление методов. А в Модели Акторов интерфейсы выражаются через перечисление сообщений.
Типобезопасность не является самоцелью, как я надеюсь. Akka, как и SObjectizer, — это инструмент для решения практических задач. Если типобезопасность в каких-то задачах становится настолько важной, что этому уделяется внимание, то должны быть какие-то примеры.
Пока же разговор идет о том, что раз мы находимся в рамках статической типизации, то хотим иметь ее везде. В том числе и при взаимодействии акторов посредством асинхронных сообщений. Только вот проблема в том, что преимущества статической типизации для синхронных вызовов — они понятны и очевидны. А вот типизация асинхронного обмена сообщениями… Не понятно, и не очевидно.
Но зачем это? Это все может решаться и без попыток подружить Модель Акторов со статической типизацией.
Если же сразу учить C++, без отсылок на C, то все гораздо проще. Когда людей учат сразу использовать std::array или std::vector вместо старых массивов, когда range-for или std::for_each+лямбда используются вместо старых for(i=0;i<ASIZE(a);++i), когда из функции возвращается std::pair<bool,std::string> вместо возврата отдельно bool-а и отдельно вручную аллоцированной строки через out-параметр и т.д., и т.п., то современный C++ осваивается гораздо легче, чем тот же C++98/03.
А зачем ее решать на этапе компиляции?
Тут скорее вопрос владения ссылкой на актора-получателя. Если ссылкой владеет только один отправитель, то никаких сложностей нет, идет обычное взаимодействие 1:1. Если ссылкой владеет N отправителей, то ничто не препятствует взаимодействию N:1. Если конкретного программиста N:1 для конкретного актора-получателя не устраивает то… То такому программисту нужно выдумывать для своей задачи какой-то аналог Rust-овских «заимствований» для actor_reference. Чтобы добиться гарантий того, что в конкретный момент времени actor_reference доступен только одному отправителю.
Только есть большие сомнения в том, что многим потребуется что-то подобное.
А зачем это нужно? В каких практических задачах это может потребоваться?
Я еще могу понять, когда на уровне контракта пытаются описать, что в ответ на сообщение X может быть получено Y или Z. Но чтобы при этом требовалось, что ответ на X должен получить только актор типа N, это уже что-то противоречащее здравому смыслу. Ибо такая типизация мешает переиспользованию акторов.
Какой-нибудь актор connection_pool сегодня используется в одном проекте, где он общается с акторами temperature_sensors, а завтра будет использоваться в другом проекте, где он общается с акторами risk_managers. Введение требований к типам агентов-consumer-ов приведет к тому, что эти требования придется удовлетворять и для temperature_sensors, и для risk_managers. Ну и зачем это нужно, если temperature_sensors и risk_managers и так будут вынуждены приспосабливаться к интерфейсу connection_pool-а на уровне сообщений acquire, release, take и failure?