Эссе о разработке игр, мышлении и книгах

Julia — это Python++?

Логотип Julia

Давно хотел посмотреть на Julia, так как встречал его (её?) упоминание в очень разных и не всегда относящихся напрямую к программированию местах. Пока изучил только документацию и ничего серьёзного на нём не писал (это будет следующим шагом), но уже хочется сказать пару слов. В соответствии с собственными заветами :-D

Изначально я планировал сделать что-то вроде сводной таблицы «плюсы и минусы Julia», но по прочтении документации передумал.

Во-первых, язык достаточно самобытен, имеет множество мелких нюансов, эффекты которых проявляются только эмерджентно, а копировать всю документацию сюда я не планирую.

Во-вторых, такие таблицы уже есть.

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

Что есть Julia

Это динамически типизированный высокоуровневый язык программирования, сильно заточенный на JIT и оптимизацию кода на основе статического описания типов.

Язык молодой: разработка начата в 2009, первый релиз случился в 2012. Julia всё ещё в стадии активной разработки, поэтому есть фичи в экспериментальном состоянии (например, поддержка многопоточности). Документации не очень много, но она достаточно подробная.

Язык вышел из научной среды и продолжает в ней развиваться. В MIT, есть специальная лаборатория, которая сотрудничает с другими университетами.

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

У меня в загашнике есть список особенностей ЯП, который я когда-нибудь сделаю. Веду я его давно и список весьма разросся. Вот Julia выглядит как ЯП, созданный по аналогичному списку.

Идеология Julia

Не путать с основными фичами (которые есть имплементация идеологии). Их я постараюсь упоминать по ходу рассказа.

На сколько я понял, Julia стоит на трёх китах:

  • Математика должны быть удобной и быстрой.
  • Сложность надо вносить туда, где она нужна, и тогда, когда она нужна.
  • Описания данных и алгоритмов должны быть ортогональны.

Удобная и быстрая математика

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

  • С языком поставляется огромное число неопределённых юникодных операторов, например A ⊗ B, которые можно использовать для подходящих математических извращений.
  • Юникод поддерживается и в именах переменных (включая верхние и нижние индексы) и даже как замена управляющих конструкций. Например, можно написать for s ∈ ["foo","bar","baz"] вместо for s in ["foo","bar","baz"]
  • Есть мощный синтаксис для векторизации вычислений (применения операций к каждому элементу контейнера без написания циклов).
  • В стандартную библиотеку интегрировано несколько известных математических библиотек на C (C++, Fortran — не разбирался). Сама библиотека содержит больше математики, чем «библиотеки других известных языков».
  • Из коробки дают функциональность для запуска распределённых вычислений.
  • И даже в частных случаях разрешили писать умножение без символа «*»: 2х вместо 2*x.

Если вам близка математика, вы будете писать кипятком.

Уместная сложность

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

Достигается это двумя путями:

Специализация типов функций

В Julia можно получить производительность (и гарантии) компилируемых статически типизированных языков. Если надо. Если не надо — пишите на обычном динамическом языке, без уточнения типов.

Причём этот механизм изначально встроен в язык, а не пришит к нему лишней ногой, после десятков лет разработки, как в Python.

Если во время исполнения Julia имеет всю информацию о типах аргументов, передаваемых в функцию, и это подходящие типы, то она в рантайме скомпилирует (один раз) специализированную версию функции для конкретного набора типов.

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

Если же вы не знаете, какие конкретно типы могут быть у аргументов или знаете, но недостаточно точно (например, знаете, что это будет некоторое представление целых чисел, но не уверены будут ли это простые int32 или объекты с реализацией длинных целых чисел), то код будет выполняться со скоростью динамического ЯП.

Специализация алгоритмов

Julia выделяет в функциях две составляющие:

  • Собственно функцию, как правило отображения входных параметров на выходные.
  • Конкретные реализации функции (методы, в терминологии Julia).

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

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

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

Ортогональность данных и алгоритмов

Благодаря множественной диспетчеризации, код на Julia выглядит гибче, чем код на ООП языках (когда метод выбирается только по типу одного аргумента — объекта-владельца). Полиморфнее, если хотите.

Такой подход позволяет разделить представление данных и алгоритмы работы с ними.

Для математики, например, это очень важно, так как операции в ней часто не принадлежат конкретным типам. Та же операция умножения явно не принадлежит ни натуральным, ни мнимым числам, и даже абстрактному числу не принадлежит, так как есть ещё умножение матрицы на скаляр и куча других случаев.

Поэтому код распадается на три составляющие:

  • Описание типов.
  • Описание базовых операций над типами (протокола взаимодействия с ними).
  • Описание алгоритмов.

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

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

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

Когда Julia может быть хороша

Когда нужно делать математику.

Когда нужны быстрые однотипные алгоритмы над разнородными объектами.

Когда программистам-любителям нужен доступ к сложным инструментам (и их не получается засунуть в GUI). В этом случае мы предоставляем набор реализованных алгоритмов с требованиями к базовым операциям над типами, о пользователям оставляем реализацию самих типов.

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

Сходу под эти случаи попадают:  математика, машинное обучение, игровая логика (поэтому я на Julia и начал смотреть внимательнее).

Когда  Julia может быть плоха

Эффективное использование языка требует грамотную декомпозицию алгоритмов, равно как и хорошую организацию иерархии типов.

Поэтому использовать Julia может быть не выгодно…

Когда под рукой нет опытных разработчиков.

Когда у разработчиков нет чёткой картины предметной области — не могут построить стройную иерархию типов. В этом случае прототипировать сложные вещи может оказаться больно из-за постоянной перестройки иерархии типов (без которой не получится использовать JIT на полную мощность). Возможно тут я ошибаюсь.

Когда решаемая задача находится в узкой предметной области. То есть код нельзя будет переиспользовать и нет сообщества с которым можно было бы кооперироваться через код.