Есть ли что-то неосуществимое в статических типах актерских моделей межпроцессного взаимодействия?
Так что я только недавно столкнулся с akka за пределами игрушечной емкости, и я не могу не заметить, что он и OTP совместно используют динамическую типизацию, несмотря на общее предпочтение scala статических типов. Я начал немного копаться и наткнулся на эту статью Уодлера, в которой описывается система типов HM через межпроцессное взаимодействие erlang. Тем не менее, ответ SO на этот вопрос относится к неспособности Уодлера и Марлоу выполнить эскиз, который они делают для связи по типу процесса. Для справки, я в основном разочарован в таком коде:
def receive = {
case "test" => log.info("received test")
case _ => log.info("received unknown message")
}
Я знаю, что на практике диализатор может принести большую пользу от реальной системы типов, но почему именно так сложно создавать статически проверенные актерные системы? Просто мы склонны писать Future
или же Observable
/Iteratee
библиотеки или Channel
-моделированный IO вместо систем акторов, когда мы используем системы типов, или есть технические трудности, которые пропустили Вадлер и Марлоу?
1 ответ
Обеспечение безопасности типов в мире актеров Akka - это то, что обсуждается и исследуется годами. Текущим проявлением этих продолжающихся усилий является API-интерфейс Akka Typed, который может быть изменен.
В дополнение к связанной документации увлекательная дискуссия по списку пользователей Akka, которая велась несколько лет назад (под названием "Как я могу примирить нетипизированных актеров с типичным программированием?"), Дает дополнительное представление о типизированных актерах. Прочитайте всю дискуссию здесь для всего контекста, но ниже приведены несколько выдержек:
От Дерека Уайета:
То, что вы испытываете, является компромиссом. Актеры обеспечивают компромисс, который вы, кажется, не принимаете во внимание; конечные точки (субъекты) не типизированы, а сообщения, которые они обрабатывают, строго типизированы.
Нельзя, чтобы Актер мог обрабатывать "что-нибудь" с помощью метода получения, зависящего от типа. С помощью программирования Actor я смог бы добавить столько посредников в поток сообщений, сколько мне нравится, и не мешать двум конечным точкам. Посредники в равной степени должны быть в неведении о том, что происходит (балансировщики нагрузки, маршрутизаторы, регистраторы, кэши, посредники, сборка разброса и т. Д.). Вы также должны иметь возможность перемещать их вокруг кластера, не нарушая конечные точки. Вы также можете настроить динамическое делегирование в Actor без необходимости действительно понимать, что происходит - например, Actor, который говорит на "основном диалекте", но делегирует что-то еще, когда он не понимает, что говорится.
Если вы хотите исключить все эти функции, тогда вы сможете получить искомый типобезопасный детерминизм (если вы остаетесь в той же JVM - пересечение JVM повлечет за собой ", черт возьми, я на самом деле говорите? "вопрос, который исключает гарантии времени компиляции)....
Короче говоря, вы отказываетесь от безопасности типов, чтобы открыть двери для целого нового набора объектов. Не хотите потерять безопасность типов? Закройте дверь:)
Из Эндре Варга:
Проблема заключается в том, что системы типов предназначены для локальных, а не распределенных вычислений. Давайте посмотрим на пример.
Вообразите актера, у которого есть три состояния, A, B и C
- В состоянии A он принимает сообщения типа X, а при получении он переходит в B
- В состоянии B он принимает сообщения типа X и Y. При получении X переходит в C, если Y, то остается в B
- В состоянии C он принимает сообщения типа Z
Теперь вы отправляете актеру, начиная с состояния A, сообщение X. Могут произойти две вещи:
- X доставляется, поэтому возможны следующие типы: {X, Y}
- X потерян, поэтому принятым типом является {X}
Пересечение этих есть {X}.
Теперь представьте, что вы отправляете еще одно сообщение X. Могут произойти три вещи:
- оба X были доставлены, поэтому принятым типом является {Z}
- только один из X был доставлен, другой потерян, поэтому принимаются следующие типы: {X, Y}
- оба X были потеряны, принятый тип {X}
Пересечение вышеупомянутых случаев - пустое множество.
Так каким должно быть локальное представление типа субъекта, которому вы отправили два сообщения типа X?
Давайте изменим пример и предположим, что потери сообщений не было, но давайте рассмотрим точку зрения другого отправителя. Этот отправитель знает, что другой отправитель отправил нам два примера X. Какие сообщения мы можем отправить? Есть три сценария:
- оба X, отправленные другим отправителем, уже прибыли, поэтому принятым типом является {Z}
- только первый X, отправленный другим отправителем, еще не прибыл, поэтому допустимые типы: {X, Y}
- X еще не пришло, принятый тип: {X}
Пересечение вышеупомянутых случаев - пустое множество.
Как видите, без получения ответа от актера, тип актера, который можно доказать, обычно ничего или ничего бесполезного. Только ответы могут передавать возможный тип актера, и даже это не может быть гарантировано, если есть параллельные отправители.
От доктора Ролана Куна:
Я рад, что вы подняли эту дискуссию, мое желание добавить некоторый уровень статической типизации в Akka так же стара, как и мое участие в проекте. Если вы загляните в прошлое 1.x, то найдете akka.actor.Channel[T], задуманный с учетом этого, а в 2.1 и 2.2 были Typed Channels как эксперимент на основе макросов. Последний фактически перешел черту от мысленного эксперимента до кода, и вы можете попробовать его, чтобы понять, как статические типы взаимодействуют с очень динамичной системой.
Основным недостатком Typed Channels была его несоответствующая сложность (слишком много параметров типов и слишком сложные типы - со списками и картами уровня типов - в них). Мы постепенно сходимся к дизайну, который может найти правильный баланс, но по сути это означает удаление
sender
от актеров Akka (что также имеет другие очень полезные преимущества, касающиеся закрытия вещей в будущих преобразованиях). Суть его заключается в параметризации ActorRef[T] в соответствии с типом сообщения, которое он принимает (с очевидными дополнительными эффектами для Props[T], Actor[T] и т. Д.). Затем субъект может представить ссылки на себя с соответствующим типом и отправить его другим субъектам - в определенных сообщениях, чтобы обойти удаление типа. Это даже позволило бы формулировать протоколы сообщений, типа сеансов или, по крайней мере, близко к ним.Дерек сделал отличное замечание о том, что модель актора действительно выигрывает от ограничения типов: маршрутизатору сообщений не обязательно нужно что-либо знать о сообщениях, проходящих через него. Насколько хорошо он работает для параметризации самого маршрутизатора, еще неизвестно, но в целом такие этапы маршрутизации уничтожат информацию о типе, и мы не сможем там ничего сделать. Ваше мнение о том, что некоторая проверка типов лучше, чем вообще никакой, хорошо мне подходит, если для разработчика эта разница очевидна: мы должны избегать ложного чувства безопасности.
Это подводит меня к действительному высказыванию Эндре о том, что параллельное поведение недоступно для статической проверки. Проблема гораздо шире, чем потеря сообщений, потому что любое недетерминированное действие должно приводить к дизъюнкции типа, убивая наши хорошие статические типы через экспоненциальный взрыв структуры типов. Это означает, что мы можем практически выразить, используя типы только те части, которые являются детерминированными: если вы отправляете сообщение типа A актеру, то вы можете получить обратно сообщение типа B (что означает необходимость предоставить ActorRef[B] в сообщении A), где A и B обычно являются типами сумм, такими как "все команды, принятые этим субъектом" и "все ответы, которые могут быть отправлены". Невозможно смоделировать качественные изменения состояния актера, потому что компилятор не может знать, произойдут ли они на самом деле или нет.
Однако есть некоторый свет: если вы получаете сообщение B, которое включает в себя ActorRef[C] от цели, то у вас есть доказательства того, что эффект сообщения A произошел, поэтому вы можете предположить, что актер сейчас находится в состоянии, когда он принимает сообщение C. Но это не гарантия, актер, возможно, потерпел крах в это время.
Обратите внимание, что все это не зависит от удаленного обмена сообщениями. Ваше желание разделить актеров на параллелизм и дистрибутивную часть очень понятно, я привык думать так же. Затем я осознал, что параллелизм и распределение - это одно и то же: процессы могут выполняться одновременно, если их выполнение разделено в пространстве или времени, что означает распределение, а с другой стороны, конечная скорость света подразумевает, что распределенные процессы будет по определению одновременно. Мы хотим инкапсуляцию и разделение для наших участников, только общение с использованием сообщений, и эта модель означает, что два участника всегда отделены друг от друга, они распределены, даже если они работают на одной и той же JVM (очереди могут заполняться, могут возникать сбои, связь не полностью надежен - хотя его надежность определенно намного выше, чем в случае сети). Если вы подумаете о современных процессорах, различные ядра и особенно сокеты также разделены сетями, они просто намного быстрее, чем гигабитный Ethernet вашего дедушки.
Именно поэтому я считаю, что модель Actor - это как раз правильная абстракция для моделирования независимых компонентов в ваших приложениях сейчас и в будущем, так как само оборудование становится все более и более распределенным, и актеры улавливают только суть этого. И, как я уже говорил выше, я вижу возможности для улучшения со стороны статической типизации вещей.