Всем известны такие прогрессивные новички в программировании как — «Go, Rust, Nim, Crystal» и все они очень круты в своих определенных областях.

К примеру:

  1. Go был рожден как супер простой и промышленный язык для быстрого решения поставленных задач с идеями, которые всем прекрасны известны, но некоторые из них прибиты к другим языкам гвоздями (На 5мм).
  2. Второй наш оппонент — это Rust, победитель по жизни, но из-за своей сложной жизни в развитии он стал для сообщества, как будущая и модная замена C++. Для меня его судьба пока не понятна, так как с зелеными потоками и IO под них там пока туго, то я его ставлю на место в ряд с C для микроконтроллеров, драйверов и операционных систем.
  3. Crystal… Прямо и четко говорю, что это супер производительный клон Ruby. Больше сказать нечего, весь он пропитан его духом.
  4. Nim (Он же Нимушка или Нимрод) и его похожесть на скриптовые языки создают ему особую атмосферу, однако внутри он достаточно сложный организм и для меня сия сущность, как Haxe с такими же ощущениями при программировании на нем.

А Pony — это моя любимая и маленькая поняшка. С виду и по названию языка можно лихо пройти мимо… В общем, приглашаю вас под капот статьи.

Это только начало


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

  • Я — Nyarum, известен, как простой пользователь языка Go. Часто задвигаю иронию с Растом и являюсь соавтором Slack сообщества гоферов.
  • Мои петпрожекты всегда связаны с эмулированием онлайн игр. От реверса до воспроизведения серверной части.
  • И я очень люблю Аниме.

Теперь вы знаете все мои секреты, можно и продолжить нашу интересную историю в мир прекрасного Pony.

Язык и краткий ликбез


image
Это тот самый жизнерадостный кормилец и он больше похож на кролика

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)


  1. stepanp
    31.10.2015 03:30
    +21

    My Little Pony это не аниме


  1. torkve
    31.10.2015 09:58

    Библиотека акторов Akka для скалы, например, предлагает возможность актору изменять в процессе работы своё состояние, изменяя набор обрабатываемых сообщений (состояния при этом ещё можно складывать/доставать из стека), таким образом удобно писать логику работы и реализовывать конечные автоматы.
    Из описания совершенно не видно этой фичи в Pony. Там предлагается всё это делать через известно что?


    1. ingrysty
      31.10.2015 13:55

      Я хоть и работал с Akka, но к сожалению плохо представляю, о чем ведется речь. Буду рад любым примерам, чтобы я мог возможно воспроизвести тоже самое на Пони.


      1. torkve
        31.10.2015 16:38

        class 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
        }
        

        Как-то так. Я уже плохо помню скалу, писал по памяти.


        1. ingrysty
          31.10.2015 16:41

          Так вы вроде обычный обмен сообщениями между акторами показали :)


          1. Optik
            31.10.2015 16:48

            Речь о become.


          1. torkve
            31.10.2015 16:52
            +1

            Нет, смотрите: наш актор бывает в двух состояниях. Изначально он в состоянии successfulPing, в котором он умеет обрабатывать сообщения Ping и Pong, отвечая на них корректным результатом. Все остальные сообщения приводят к ошибке (и, кажется, то ли просто игнорируются аккой, то ли приводят к падению актора, не помню). При этом пинги кроме ответа переводят актор во второе состояние — failPing, в котором мы умеем отвечать на пинг промахом и переходить в первое состояние, а на все остальные сообщения отвечаем отправителю «печалька».
            То есть, поведением актора управляет не какая-то сохранённая переменная, а просто выбранная функция-обработчик, которая умеет обрабатывать какие-то типы. Если бы я придумал какую-то более сложную логику, я бы мог написать в этих двух функциях вообще непересекающийся набор типов сообщений, которые они обрабатывают. Таким образом в акке легко можно сделать конечный автомат, который будет переходить между состояниями, и не надо будет писать какую-то сложную логику, которая будет проверять состояние перед обработкой всех возможных типов сообщений. Состоянием будет являться сама функция-обработчик актора, которой достаточно будет знать только доступные именно ей переходы.


            1. ingrysty
              31.10.2015 21:38
              +1

              Спасибо за доступное объяснение.
              На данный момент такой прозрачности нету, однако задача меня заинтересовала, поэтому попробую набросать что нибудь взаимозаменяемое и удобное.


            1. qnikst
              02.11.2015 10:47

              > Все остальные сообщения приводят к ошибке (и, кажется, то ли просто игнорируются аккой, то ли приводят к падению актора, не помню).

              вот это важно, например, в cloud haskell, есть примитив receiveWait, который позволяет обрабатывать сообщения таким же образом, при этом можно явно контролировть, что произойдёт с остальными сообщениями (в случае добавления matchAny они будут прочитаны из MailBox и их можно обработать, иначе они там остаются).

              В остальном, никакой разницы между Erlang (насколько я его знаю) или Haskell я лично не заметил, код так пишут всегда…


              1. torkve
                02.11.2015 11:52

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


              1. senia
                02.11.2015 17:02

                при этом можно явно контролировть, что произойдёт с остальными сообщениями (в случае добавления matchAny они будут прочитаны из MailBox и их можно обработать, иначе они там остаются).

                В scala акторах (когда они еще были) было такое же поведение.
                В akka его признали не эффективным и по умолчанию убрали. Его можно вернуть при помощи Stash.
                По умолчанию же все не обработанные сообщения попадают в метод unhandled, который, если не вдаваться в подробности, игнорирует все сообщения.


  1. mmvds
    31.10.2015 11:01

    Crystal… Прямо и четко говорю, что это супер производительный клон Ruby. Больше сказать нечего, весь он пропитан его духом.
    я бы добавил что очень и ОЧЕНЬ сырой клон Ruby


  1. lega
    31.10.2015 11:42
    +1

    «Go, Rust, Nim, Crystal» и, все они очень круты в своих определенных областях.
    А в какой области крут Pony?
    Какие языки Pony пытается потеснить? Go, DLang?
    Как у него с производительностью?


    1. ingrysty
      31.10.2015 13:48

      Как ни странно, на все вопросы существуют ответы в статье. К примеру, 2 пейпера расскажут о бенчмарках и производительности в сравнении тех же Scala, Akka, Erlang.


      1. Optik
        31.10.2015 14:11
        +1

        Бесполезный бенчмарк для сравнительной оценки. Не указаны настройки противопоставляемых систем. Не понятно что по оси ординат откладывается, хотя мож я просмотрел. Вот к примеру покрытый пылью времени результат акка, который даже с учетом разности в железе имеет большую пропускную способность и даже несмотря на наличие stw.


        1. ingrysty
          31.10.2015 14:22

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


  1. Optik
    31.10.2015 12:04
    +2

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

    Дженерики здесь совершенно не причем и на кортеж оно не фига не похоже. Оба есть ADT.


    1. ingrysty
      31.10.2015 13:41

      Многие моменты исходили из официальной документации, в том числе про похожесть на кортежи и вестимо, в плане семантики. Дженерики — своя отсебятина, поэтому вполне допускаю свою ошибку ;)


  1. kekekeks
    31.10.2015 14:01
    +2

    сейчас скорость передачи между акторами достигает 20кк в секунду
    А чего так медленно? Судя по бенчмаркам Akka.NET у них 28КК было в начале года. У JVM-овской акки 50КК, ибо кодовую базу успели нормально оптимизировать.


  1. bohdan4ik
    31.10.2015 14:09

    Оп. Пони на хабре! :)

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

    У меня вот вопрос есть: как определить, что же за ошибка произошла, если метод может упасть по нескольким условиям?
    Примерно так:

    fun a()? {
    if 1 then error end
    if 2 then error end
    }

    Как в методе, вызывающим a() узнать, что это была за ошибка: 1 или 2? Было дело, я гуглил, и встречал в обсуждениях что-то похожее на «если метод возвращает несколько ошибок — это неправильный метод». Но ведь это… Неправильно же.


    1. 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
      


  1. AndersonDunai
    01.11.2015 02:28
    +3

    Одинарная кавычка оставила неприятный осадок на целую ночь.


  1. sashkin
    02.11.2015 17:50

    Ни статья, ни беглый просмотр офсайта не говорят о том что это, очередной язык для jvm или нативный компилятор?


    1. nwalker
      02.11.2015 18:22

      Нативный, компилируется через LLVM.