К примеру:
- Go был рожден как супер простой и промышленный язык для быстрого решения поставленных задач с идеями, которые всем прекрасны известны, но некоторые из них прибиты к другим языкам гвоздями (На 5мм).
- Второй наш оппонент — это Rust, победитель по жизни, но из-за своей сложной жизни в развитии он стал для сообщества, как будущая и модная замена C++. Для меня его судьба пока не понятна, так как с зелеными потоками и IO под них там пока туго, то я его ставлю на место в ряд с C для микроконтроллеров, драйверов и операционных систем.
- Crystal… Прямо и четко говорю, что это супер производительный клон Ruby. Больше сказать нечего, весь он пропитан его духом.
- Nim (Он же Нимушка или Нимрод) и его похожесть на скриптовые языки создают ему особую атмосферу, однако внутри он достаточно сложный организм и для меня сия сущность, как Haxe с такими же ощущениями при программировании на нем.
А Pony — это моя любимая и маленькая поняшка. С виду и по названию языка можно лихо пройти мимо… В общем, приглашаю вас под капот статьи.
Это только начало
Предварительно расскажу немного о себе, чтобы создать вам особую атмосферу для понимания языка и его возможных применений.
- Я — Nyarum, известен, как простой пользователь языка Go. Часто задвигаю иронию с Растом и являюсь соавтором Slack сообщества гоферов.
- Мои петпрожекты всегда связаны с эмулированием онлайн игр. От реверса до воспроизведения серверной части.
- И я очень люблю Аниме.
Теперь вы знаете все мои секреты, можно и продолжить нашу интересную историю в мир прекрасного Pony.
Язык и краткий ликбез
Это тот самый жизнерадостный кормилец и он больше похож на кролика
Pony — объектно-ориентированный язык программирования, построенный в первую очередь на модели акторов и прозрачной конкурентности. В его дополнительных плюсах трутся такие понятия, как — «Open-source, производительность, интересные идеи». Основной упор идет на многоядерный системы, современную разработку без мыслей о низком уровне и очень производительную конкурентность.
Возможности
- Типобезопасность. Подтвержденная математическим бекграундом.
- Безопасная работа с памятью. Хоть это и является следствием из типобезопасности, но нельзя упускать данный пункт. В нем вы не найдете повисших указателей, переполнение буффера или в самом частом случае — вам не придется знать, что такое null.
- Безопасная работа с экзепшенами. Все они определяются на семантическом уровне и никаких рантайм неожиданностей.
- Скажем нет гонкам данных! Никаких атомиков, мьютексов или таймеров. Система типов с проверками во время компиляции дают высокую гарантию сего понятия. Пишите конкурентный код и не переживайте за остальное.
- Никаких дедлоков. Следствие из отсутствия любых операций с блокировками.
Краткая спецификация по языку
Система типов:
- Класс, стандартен из ООП мира. Поля, конструкторы и функции в вашем распоряжении.
class Habra let _name: String new create(name': String) => _name = name' fun name(): String => _name
- Актор, идентичен классу, но имеет возможности определения асинхронных функций. Это относится к прозрачной конкурентности.
actor Habro let name: String var _hunger_level: U64 = 0 new create(name': String) => name = name' be eat(amount: U64) => _hunger_level = _hunger_level - amount.min(_hunger_level)
- Примитив, тоже идентичен классу с 2-я различиями. Он не может иметь полей и в процессе работы программы создается только 1 инстанс на определенный вами примитив. Используется для многих вещей, но чаще, как особый вид дженерика с возможностью в None тип, чтобы проверять приходящие из FFI мира значения, не равняется ли оно пустоте.
primitive _Habras
- Трейт и интерфейс (Являются подтипами, однако определяются так же, как и класс). Многие ЯП имеют только 1 из них одновременно, но в Пони они задействованы оба. Различие в том, что трейт является условной проверкой принадлежности, а интерфейс проверяет соответствие структурно.
// Trait trait Family fun age(): U64 => 5 class Habravi is Family // Interface interface Habrovik fun name(): String class Habrovichek fun name(): String => "Model #1"
- Алиасы для типов. Не буду говорить тут про них много, мощная система и требует самостоятельного изучения.
// Enumeration primitive Red primitive Blue primitive Green type Colour is (Red | Blue | Green) // Complex interface HasName fun name(): String interface HasAge fun age(): U32 interface HasAddress fun address(): String type Person is (HasName & HasAge & HasAddress)
- Кортеж есть последовательность типов, которые могут быть определены в одной переменной.
var x: (String, U64) x = ("hi", 3)
- Юнион очень похож на кортеж, только используется для обобщения возможных типов. Особый вид дженерика.
var x: (String | U64) x = "hello habr" // or x = 5
- Пересечение почти является противоположностью юниона и может описывать одно значение для нескольких типов одновременно. В примере ниже видно, как карта может содержать одновременно ключ двух разных типов.
type Map[K: (Hashable box & Comparable[K] box), V] is HashMap[K, V, HashEq[K]]
- Все выше описанные выражения типов могут комбинироваться
// Tuple in Union which in Array var _array: Array[((K, V) | _MapEmpty | _MapDeleted)]
Стандартные выражения:
- Это всем очень хорошо знакомые — «Переменные, знаки операций, проверки, циклы, методы, экзепшены, etc..». Оставляю на самостоятельное изучение.
Возможности:
- Объектные — Владение низким уровнем для вас ограничено, FFI является исключением и может сломать ваш код, но это контролируемо. Никаких глобальных переменных и функций.
- Ссылочные — построены на нескольких базовых концептах и для контроля (Гарантии) используются 3-х буквенные ключевики.
- iso — Полная изоляция, другие переменные не смогут иметь доступ к этим данным. Может быть изменена, как вашей душе угодна и передана в другие акторы.
- val — Неизменяемые данные, соответственно переменная под данной защитой доступна для чтения и может быть передана в другие акторы.
- ref — Изменяемые данные, можно вертеть в любые стороны и иметь несколько переменных на эти данные, но не возможна передача в другие акторы.
- box — Это совокупность val и ref, в зависимости от обстоятельств. Если данные используются только для чтения, то переменная на них ведет себя, как val и может быть передана в другие акторы. Однако, если вы пробуете записать новые данные в нее, то получаете ref и не возможность использовать между несколькими акторами.
- trn — Данные, в которые можно писать постоянно, но в другие переменные отдается, как box. В последствии вы можете поменять ограничитель на val и передать в другие акторы.
- tag — Идентификация данных, вы не можете писать в них или читать, но вполне возможно хранить и сравнивать для определения типа данных. Возможна передача в другие акторы.
String iso // An isolated string String trn // A transition string String ref // A string reference String val // A string value String box // A string box String tag // A string tag
Привлекательность языка
Сборщик мусора
На борту мы имеем очень крутой GC, который является полностью конкурентным и не имеет Stop the World. На самом деле там их 2, один является сборщиком ссылок для каждого созданного актора и один глобальный.
Скорость акторов
Благодаря такому парню, как Дима Вьюков, который внес огромный вклад в lock-free алгоритмы и Go — появилась база, на которую делал упор Pony при разработке общения акторов. И именно поэтому сейчас скорость передачи между акторами достигает 20кк в секунду, а создание может достигать 1кк в секунду.
Прозрачная конкурентность
Это понятие дал я сам и был удивлен, когда для того, чтобы сделать код конкурентным — нам потребуется всего лишь поменять название функции. Этот способ еще более современней, чем предоставил Go со своими горутинами.
Пейперы
Статус и дополнительные ссылки
Язык находится в статусе далекой беты, сейчас версия языка 0.2.1. Разработчики на данный момент заканчивают остальные планируемые фичи, исправляют баги и стабилизируют язык. Плагины есть почти под все популярные редакторы.
Благодарен, что вы прочли статью и, возможно, заинтересовались языком.
Комментарии (24)
torkve
31.10.2015 09:58Библиотека акторов Akka для скалы, например, предлагает возможность актору изменять в процессе работы своё состояние, изменяя набор обрабатываемых сообщений (состояния при этом ещё можно складывать/доставать из стека), таким образом удобно писать логику работы и реализовывать конечные автоматы.
Из описания совершенно не видно этой фичи в Pony. Там предлагается всё это делать через известно что?ingrysty
31.10.2015 13:55Я хоть и работал с Akka, но к сожалению плохо представляю, о чем ведется речь. Буду рад любым примерам, чтобы я мог возможно воспроизвести тоже самое на Пони.
torkve
31.10.2015 16:38class MyFlappingPingPongActor extends Actor { case class Ping() case class Pong() case class Miss() case class Failure(msg: String) def successfulPing: Receive = { case Ping => { sender ! Pong() context become failPing } case Pong => { sender ! Ping() } } def failPing: Receive = { case Ping => { sender ! Miss() context become successfulPing } case _ => { sender ! Failure("I'm sad, no actions until I'm happy again!") } } def receive = sucsessfulPing }
Как-то так. Я уже плохо помню скалу, писал по памяти.ingrysty
31.10.2015 16:41Так вы вроде обычный обмен сообщениями между акторами показали :)
torkve
31.10.2015 16:52+1Нет, смотрите: наш актор бывает в двух состояниях. Изначально он в состоянии successfulPing, в котором он умеет обрабатывать сообщения Ping и Pong, отвечая на них корректным результатом. Все остальные сообщения приводят к ошибке (и, кажется, то ли просто игнорируются аккой, то ли приводят к падению актора, не помню). При этом пинги кроме ответа переводят актор во второе состояние — failPing, в котором мы умеем отвечать на пинг промахом и переходить в первое состояние, а на все остальные сообщения отвечаем отправителю «печалька».
То есть, поведением актора управляет не какая-то сохранённая переменная, а просто выбранная функция-обработчик, которая умеет обрабатывать какие-то типы. Если бы я придумал какую-то более сложную логику, я бы мог написать в этих двух функциях вообще непересекающийся набор типов сообщений, которые они обрабатывают. Таким образом в акке легко можно сделать конечный автомат, который будет переходить между состояниями, и не надо будет писать какую-то сложную логику, которая будет проверять состояние перед обработкой всех возможных типов сообщений. Состоянием будет являться сама функция-обработчик актора, которой достаточно будет знать только доступные именно ей переходы.ingrysty
31.10.2015 21:38+1Спасибо за доступное объяснение.
На данный момент такой прозрачности нету, однако задача меня заинтересовала, поэтому попробую набросать что нибудь взаимозаменяемое и удобное.
qnikst
02.11.2015 10:47> Все остальные сообщения приводят к ошибке (и, кажется, то ли просто игнорируются аккой, то ли приводят к падению актора, не помню).
вот это важно, например, в cloud haskell, есть примитив receiveWait, который позволяет обрабатывать сообщения таким же образом, при этом можно явно контролировть, что произойдёт с остальными сообщениями (в случае добавления matchAny они будут прочитаны из MailBox и их можно обработать, иначе они там остаются).
В остальном, никакой разницы между Erlang (насколько я его знаю) или Haskell я лично не заметил, код так пишут всегда…torkve
02.11.2015 11:52Я не хочу врать, но возможно, что в акке это вообще настраивается выставленными политиками — так же, как и разнообразные политики доставки, что делать при падении актора и т.п.
В пони этого всего я не вижу.
senia
02.11.2015 17:02при этом можно явно контролировть, что произойдёт с остальными сообщениями (в случае добавления matchAny они будут прочитаны из MailBox и их можно обработать, иначе они там остаются).
В scala акторах (когда они еще были) было такое же поведение.
В akka его признали не эффективным и по умолчанию убрали. Его можно вернуть при помощи Stash.
По умолчанию же все не обработанные сообщения попадают в метод unhandled, который, если не вдаваться в подробности, игнорирует все сообщения.
mmvds
31.10.2015 11:01Crystal… Прямо и четко говорю, что это супер производительный клон Ruby. Больше сказать нечего, весь он пропитан его духом.
я бы добавил что очень и ОЧЕНЬ сырой клон Ruby
lega
31.10.2015 11:42+1«Go, Rust, Nim, Crystal» и, все они очень круты в своих определенных областях.
А в какой области крут Pony?
Какие языки Pony пытается потеснить? Go, DLang?
Как у него с производительностью?ingrysty
31.10.2015 13:48Как ни странно, на все вопросы существуют ответы в статье. К примеру, 2 пейпера расскажут о бенчмарках и производительности в сравнении тех же Scala, Akka, Erlang.
Optik
31.10.2015 14:11+1Бесполезный бенчмарк для сравнительной оценки. Не указаны настройки противопоставляемых систем. Не понятно что по оси ординат откладывается, хотя мож я просмотрел. Вот к примеру покрытый пылью времени результат акка, который даже с учетом разности в железе имеет большую пропускную способность и даже несмотря на наличие stw.
ingrysty
31.10.2015 14:22Графические бенчмарки подразумевают под собой одно окружение. На абсциссе указано повышение ядер, на ординате скорость относительно Erlang.
Текстовые бенчмарки из другого пейпера имеют описание систем, на которых тестировалось максимальное количество сообщений в секунду. И там далеко не монстр, в сравнении с тем, что указан по вашей ссылке.
Optik
31.10.2015 12:04+2Юнион очень похож на кортеж, только используется для обобщения возможных типов. Особый вид дженерика.
Дженерики здесь совершенно не причем и на кортеж оно не фига не похоже. Оба есть ADT.ingrysty
31.10.2015 13:41Многие моменты исходили из официальной документации, в том числе про похожесть на кортежи и вестимо, в плане семантики. Дженерики — своя отсебятина, поэтому вполне допускаю свою ошибку ;)
kekekeks
31.10.2015 14:01+2сейчас скорость передачи между акторами достигает 20кк в секунду
А чего так медленно? Судя по бенчмаркам Akka.NET у них 28КК было в начале года. У JVM-овской акки 50КК, ибо кодовую базу успели нормально оптимизировать.
bohdan4ik
31.10.2015 14:09Оп. Пони на хабре! :)
Поглядываю на этот язык, очень и очень нравится, но чего-то реального пока совсем не пробовал писать.
У меня вот вопрос есть: как определить, что же за ошибка произошла, если метод может упасть по нескольким условиям?
Примерно так:
fun a()? {
if 1 then error end
if 2 then error end
}
Как в методе, вызывающим a() узнать, что это была за ошибка: 1 или 2? Было дело, я гуглил, и встречал в обсуждениях что-то похожее на «если метод возвращает несколько ошибок — это неправильный метод». Но ведь это… Неправильно же.ingrysty
31.10.2015 14:51Сейчас есть непонятная ситуация со стектрейсом, но предполагаю, что он будет.
А пока можно отслеживать принтами перед возникновением ошибки и в реальных программах — обеспечивать правильный вывод ошибок, через примитивы (Очень хороший пример у них в options пакете).
actor Main var _env: Env new create(env: Env) => _env = env env.out.print("Hello, sandbox.") try err(1) // or err(2) else env.out.print("Error in program") end fun err(n: U64) ? => if n == 1 then _env.out.print("Error in one if") error end if n == 2 then _env.out.print("Error in two if") error end
stepanp
My Little Pony это не аниме