Lua in Moscow 2019: интервью с Роберто Иерусалимским

2e2088efd08efa95d7b7d335d3d59696.jpg

Некоторое время назад наш московский офис посетил Роберто Иерусалимский, автор языка Lua. Мы взяли у него интервью, в ходе которого задали и вопросы от читателей Хабра. Наконец-то мы можем поделиться с вами всей записью разговора.
— Давайте для начала пофилософствуем. Если бы вы снова создавали Lua с нуля, какие три вещи вы изменили бы в этом языке?

— Ого! Трудный вопрос. За созданием и разработкой языка стоит целая история. Это не было каким-то одним большим решением. Были решения, о которых я сожалел и которые с годами смог исправить. И программисты всё время на это жалуются, потому что сломалась совместимость. Мы поступали так несколько раз. А я размышляю лишь о каких-то небольших задачах.

— Глобальность по умолчанию (Global-by-default)? Думаете, это верный путь?

— Может быть. Но этот путь очень труден для динамических языков. Возможно, следует совсем отказаться от понятия «по умолчанию», но тогда будет тяжело использовать переменные.
Например, вам придётся как-то объявлять все стандартные библиотеки. Вам нужны one-liner, print(sin(x)), а потом вам нужно объявить «print» и ещё «sin». Странно иметь объявления для таких коротеньких скриптов.

Всё, что крупнее, не должно иметь ничего по умолчанию, как мне кажется. Локальность по умолчанию (Local-by-default) — это не решение, она вообще не существует. Она только для присваивания, а не для использования. Мы что-нибудь присваиваем, затем используем и снова присваиваем, и возникает совершенно необъяснимая ошибка.

Возможно, глобальность по умолчанию не идеальна, но локальность по умолчанию точно не вариант. Думаю, какое-то объявление, возможно, опциональное… Мы много раз намеревались сделать какое-то глобально объявление. Но если мы будем слепо внедрять всё, о чём нас просят, ничего хорошего не выйдет

(саркастически) Да, мы соберёмся внедрить глобальное объявление, добавим то-сё, пятое, десятое, вытащим другое, и в результате поймём, что финальное решение не удовлетворяет большинство программистов, и мы не добавим все возможности, о которых нас просят, и поэтому мы не внедряем ничего. В конце концов, строгий режим (strict mode) — это разумный компромисс.

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

— Глобальность по умолчанию ещё удобна для маленьких конфигурационных файлов.

— Да, верно, для маленьких скриптов и тому подобного.

— И никаких компромиссов в данном случае?

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

— И тут мы возвращаемся к первому большому вопросу: какие три вещи вы изменили бы, будь такая возможность? Как мне представляется, вас вполне устраивает существующее положение вещей, верно?

— Ну, я бы не назвал это большим изменением, и всё же… Одним из неудачных решений, которое стало большим изменением, это nil в таблицах (nils in tables). Я об этом сожалею. Я сделал этот хак… Вы знаете, о чём я говорю? C полгода-год назад я выпустил версию Lua, в которой были nil в таблицах.

— Nil-значения?

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

— Зачем это понадобилось?

— Я действительно убежден, что это целая проблема… Я думаю, что большинство проблем с nil в массивах исчезли бы, если бы мы могли иметь [nil в таблицах]… Точнее, проблема не с nil в массивах. Мне говорят, что в массивах не может быть nil, поэтому массивы должны быть отделены от таблиц. Настоящая проблема в том, что мы не можем держать nil в таблицах! Поэтому проблема заключается не в том, как мы представляем массивы, а в таблицах. Если бы мы могли иметь nil в таблицах, то имели бы nil в массивах без всего остального. Так что об этом я очень сожалею, и многие люди не понимают, как всё могло бы измениться, если бы Lua позволял размещать nil в таблицах.

— Могу я рассказать одну историю про Tarantool? У нас есть своя реализация null, представляющая собой CDATA на null-указатель. Мы используем её в тех случаях, когда нужно создавать в памяти пустые фрагменты для вставки аргументов позиционирования при удалённых вызовах, и так далее. Но обычно с этим возникают проблемы, потому что CDATA всегда преобразуется в «true». И nil в массивах позволил бы решить много проблем.

— Да, я знаю. Именно об этом я и говорю — это решило бы массу проблем для многих, но это ведет к ещё большей проблеме совместимости. У нас не хватает смелости выпустить версию, которая будет настолько несовместима, а затем разрушить сообщество и выпустить другую документацию для Lua 5, Lua 6 и т.д. Но, может быть, однажды мы выпустим такую версию. Это были бы очень большие перемены. Считаю, что так нужно было поступить с самого начала, и тогда мы бы говорили о тривиальном изменении в языке, если не считать совместимости. А сегодня это поломает множество программ.

— Какие вы видите недостатки кроме вопроса совместимости?

— Понадобятся две новые примитивные функции. Что-то вроде «клавиши Delete», потому что присвоение nil не удалит ключ, так что нужна будет примитивная операция для его настоящего удаления из таблицы. И понадобится «test» для проверки, где именно есть отличие между nil и отсутствием данных.

— Вы анализировали влияние этих нововведений в практических реализациях?

— Да, мы выпустили такую версию Lua. Как я говорил, это незаметно сломало код. Некоторые применяют вызов функции table.insert(f(x)). И намеренно сделано так, что если функция ничего не хочет вставлять, она возвращает nil. Так что вместо отдельной проверки «хочу ли я вставлять?» я вызываю table.insert, и если получаю nil, то знаю, что вставляться не будет. Как и в любом другом языке, баг превратился в фичу, и люди ею пользуются. Но если фичу изменить, это сломает код.

— А что насчёт типа void? Как nil, только void?

— Ну нет, это кошмар. Вы просто откладываете решение проблемы. Если вы вводите один «костыль», то потом вам понадобится ещё один, и ещё, и ещё. Это не решение. Одна из проблем в том, что nil уже укоренился во многих местах языка. Вот типичный пример: мы говорим — «избегайте nil в массивах, то есть дыр». А потом пишем функцию, которая возвращает nil и что-то после него, и получаем ошибочный код. Такая конструкция уже сама подразумевает, что собой представляет nil… Например, если я хочу сделать список из результатов, возвращённых функцией, только для того, чтобы их все поймать.

— Для этого у вас есть хак:)

— Совершенно верно, но вы не обязаны использовать хаки для решения таких простых и очевидных задач. Однако библиотеки делают так… Как-то я думал об этом: возможно, библиотеки должны вместо nil возвращать false, хотя это сырой вариант, он решит лишь малую часть проблемы. Настоящая проблема, как я говорил, в том, что нам нужно иметь nil в таблицах. В противном случае нам не стоит так часто использовать nil, как мне кажется. В общем, непонятно. Так что если вы создали void, эти функции всё равно будут возвращать nil, и мы не решим проблему, пока не создадим новый тип и функции не будут возвращать void вместо nil.

— С помощью void можно явно сказать, что ключ должен храниться в таблице, ключ со значением void. А nil может работать, как и раньше.

— Да, это я и имею в виду. Все эти функции в библиотеках должны возвращать void или nil.

— Они могут также возвращать и nil, почему бы и нет?

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

— Но у нас не будет первого ключа, только второй.

— Нет, второго не будет, потому что подсчёт будет вестись неверно и в массиве появится дыра.

— То есть вы хотите сказать, нужен фальшивый метаметод?

— Да. Я мечтаю о подобном:

{f(x)}

Нужно перехватить все результаты, возвращаемые функцией f(x). А затем я смогу сказать %x или #x, и таким образом узнаю количество возвращённых результатов. Вот как должен работать нормальный язык. Так что создание void проблемы не решит, пока не будет очень строго правила, что функции никогда не возвращают nil. Но тогда зачем нам вообще nil? Быть может, стоит от него отказаться.

— Роберто, будет ли в Lua гораздо более сильная поддержка статического анализа? Вроде «Lua Check на стероидах»? Конечно, я понимаю, это не решит всех проблем. Вы говорили, что эта фича появится в версии 6.0, если вообще появится, верно? И если в 5.х будет мощный инструмент статического анализа — если в него будут вложены человеко-часы, — то поможет ли это?

— Нет, я считаю, что действительно мощный инструмент статического анализа называется… системой типов! Если вам нужен действительно мощный инструмент, используйте статически типизированный язык, вроде Haskell, или вообще какой-нибудь язык с зависимыми типами. Тогда у вас точно будет мощный инструмент для анализа.

— Но тогда у меня не будет Lua.

— Верно, Lua нужен для…

— Неопределённости? Мне нравится ваша картинка с жирафом про статические и динамические типы.

— Ага, это мой последний слайд из презентации.

ac966a5b2ccf1b45ab798727ee382617.jpg
Последний слайд презентации для выступления Роберто Иерусалимского «Почему Lua? (и почему нет)» на конференции Lua in Moscow 2019.

— Чтобы задать следующий вопрос, вернёмся к этой картинке. Если я правильно понял, вы считаете, что Lua — это маленький удобный инструмент для решения не очень крупных задач.

— Нет, я считаю, что вы можете решать какие-то большие задачи, но не подразумевающие статический анализ. Я всей душой верю в тесты. Кстати, я не согласен с вами относительно покрытия, что мы не должны гнаться за покрытием… Я хочу сказать, я полностью согласен с мнением, что покрытие не обеспечивает полного тестирования, однако отсутствие покрытия вообще не позволяет тестировать. Я выступал в Стокгольме по поводу тестовой комнаты, вы там были. Я начал тестировать с [несколькими] багами — это самое странное, — один из них был широко известен, а остальные совершенно неизвестны. Что-то полностью сломалось в заголовочном файле Microsoft, C и C++. Я поискал в сети, но никто об этом либо не беспокоился, либо даже не замечал этого.

К примеру, есть математическая функция modf (), которой нужно передать указатель на double-значение, потому что она возвращает два double. Мы преобразуем целую или дробную часть числа. Эта функция давно являлась частью стандартной библиотеки. Затем появилась C 99, и эта функция теперь нужна для float-чисел. А заголовочный файл Microsoft просто сохранял эту функцию и объявлял ещё одну в качестве макроса. То есть первая функция подвергалась неявному преобразованию типов. Double преобразовывалось во float, а затем указатель на double преобразовывался в указатель на float!

— С этой картинкой что-то не так.

— Это заголовочный файл из Visual C++ и Visual C 2007. Если ты вызываешь эту функцию один раз, с любыми параметрами, и затем проверяешь результат, то он будет ошибочным, если только это не 0. Любое другое значение не будет верным. Вы никогда не сможете использовать эту функцию. Нулевое покрытие. А потом было много дискуссий по поводу тестирования… Просто однократно вызываешь функцию и проверяешь результат! Эта проблема существовала долгое время, многие годы это никого не волновало. У Apple был очень известный баг, что-то вроде »if… what… goto… ok». Кто-то вставил сюда другое выражение, а всё стало нормально. И потом много спорили о том, нужны ли правила, должны ли в стиле кода использоваться фигурные скобки, и так далее, и тому подобное. Никто не упомянул о том, что есть и много других «если». Никто этого не сделал…

— Там ещё и проблема с безопасностью, насколько я помню.

— Совершенно верно. Ведь они тестируют только подтверждённые ситуации. Они не тестируют всё подряд, потому что всё будет подтверждено. Это означает, что в приложении, проверяющем безопасность, нет ни одного теста, который бы проверял отказ в каких-то соединениях, или в чём там должно быть отказано. А все спорят о том, нужны ли скобки… Нужны тесты, как минимум тесты! Ведь никто даже не тестировал то, что я подразумеваю под «покрытием». Это невероятно, люди даже не прогоняют базовые тесты. Конечно, очень тяжело было бы обеспечить покрытие базовыми тестами, прогнать все строки кода. Люди избегают даже базовых тестов, так что покрытие минимальное. Я хочу призвать вас обратить внимание на те части программы, о которых вы забыли. Что-то вроде подсказки, как можно немного улучшить ваши тесты.

— Знаете, какое покрытие тестами в Tarantool? 83%! А какое покрытие в Lua?

— Около 99,6%. Сколько у вас строк кода? Миллион, сотни тысяч? Это огромное количество. 1% от ста тысяч — это тысяча строк кода, которые никогда не тестировались. Вы вообще их не исполняли. Ваши пользователи ничего не тестируют.

— То есть около 17% функций Tarantool сейчас не используются?

— Вряд ли вы захотите снова вернуться к тому, о чём мы говорили… Я думаю, одна из проблем динамических языков (да и статических тоже) заключается в том, что люди не тестируют свой код. Даже используя статический язык, — не наподобие Haskell, а что-то вроде Coq, — то пока у вас не будет системы проверки, вы будете менять то на это. И никакой инструмент статического анализа не сможет выловить эти ошибки, поэтому вам нужны тесты. А если они у вас есть, вы сможете выявить глобальные проблемы, опечатки в именах и так далее, всевозможные ошибки. Вам обязательно нужны тесты. Иногда отлаживать будет сложно, иного просто — зависит от языка и вида бага. Но суть в том, что никакой инструмент статического анализа не заменит тестов. С другой стороны, они не гарантируют отсутствие ошибок, но с ними я чувствую себя куда увереннее.

— У нас вопрос о тестировании Lua-модулей. Я, как разработчик, хочу тестировать какие-то локальные функции, которыми позднее могу воспользоваться. Вопрос: нужно покрытие на уровне 99%, но количество функциональных ситуаций, которые должен сгенерировать модуль для API, гораздо меньше количества функциональности, которую он поддерживает внутренне.

— Простите, почему же?

— Есть функциональность, которая недоступна для публичных интерфейсов.

— Её там быть не должно, просто удалите этот код.

— Просто удалить?

— Да, я так иногда делаю в Lua. Было какое-то покрытие кода, я не мог попасть сюда, туда или туда, я решил, что это невозможно и просто удалил код. Редко, но такое бывает. Если какие-то ситуации невозможны, просто пишите в комментарии, почему это невозможно. Если не можете из публичного API попасть внутрь функции, её быть не должно. Нужно писать публичные API с ошибочными входными данными, это важно для тестов.

— Удалять код хорошо, это уменьшает сложность. Меньше сложность — выше стабильность и удобство сопровождения. Не усложняйте.

— Да, в экстремальном программировании существует такое правило. Если что-то нельзя протестировать, оно не существует.

— Какими языками вы вдохновлялись при создании Lua? Какие парадигмы, или функциональные особенности, или части каких языков вам нравятся?

— Я проектировал Lua для очень узкого назначения, это не был академический проект. Поэтому, когда вы спросили меня о повторном создании языка, я ответил, что за этим стоит целая история. Я не начинал разрабатывать Lua с «создам язык, который мне нравится, или который хочу использовать, или который всем нужен». У меня была проблема: здесь нужен язык конфигурирования для геологов и инженеров, он должен быть маленьким и с простым интерфейсом, чтобы они могли им пользоваться. Поэтому API всегда был составной частью языка. Такова была задача. Что касается вдохновения, в то время я был знаком примерно с десятью разными языками.

— Мне интересно, какие языки вы хотели включить в Lua.

— Я позаимствовал идеи из многих языков, всё, что подходило для решения моей задачи. Наибольшее влияние оказал синтаксис языка Modula, хотя трудно сказать, ведь языков так много. Что-то взял из AWK. Конечно, из Scheme и Lisp… Я неравнодушен к Lisp с тех пор, как стал программировать.

— И в Lua до сих пор нет макросов!

— Да, синтаксис получился совсем другим. Наверное, первым языком был Fortran… нет, моим первым языком был ассемблер, а затем Fortran. Я изучал, но никогда не пользовался CLU. Много программировал на Smalltalk и SNOBOL. Также изучал, но не использовал Icon, тоже очень интересный язык. Многое позаимствовал из Pascal и С. Когда я создавал Lua, C++ был для меня уже слишком сложен, это было ещё до шаблонов и прочего. Я начал работу в 1991-м и выпустил Lua в 1993-м.

— Развалился СССР и вы начали создавать Lua:) Вам не нравились точки с запятой и объекты, когда вы приступили к Lua? Мне казалось, его синтаксис будет аналогичен С, потому что Lua интегрирован в него. Однако…

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

Это действительно забавно, и связано с ответом про начинающиеся с единицы массивы, который вы не дали мне высказать [на конференции]. Мой ответ был слишком длинным.

Когда был выпущен Lua, мир был другим. Не все языки были похожи на С. Ещё не было Java и JavaScript, Python был младенцем и не перевалил за версию 1.0. Так что не все языки были похожи на С, его синтаксис был одним из многих.

То же самое и с массивами. Забавно, что большинство этого не осознаёт. У массивов, начинающихся с 0 и с 1, есть свои достоинства.

Именно благодаря С в большинстве популярных языков сегодня используются массивы, начинающиеся с 0. Их создатели вдохновлялись С. И забавно, что в С нет индексации. Вы не можете сказать, что С индексирует массивы с нуля, потому что операции индексирования там не существует. В этом языке есть арифметические операции с указателями, так что ноль в С — это не индекс, а смещение (offset). А раз это смещение, оно должно быть нулевым не потому, что так проще вычислять, а потому, что это естественно.

И все языки, скопировавшие С, имеют индексы и не имеют арифметических операций с указателями. Java, JavaScript и многие другие — ни в одном нет таких операций. Их авторы просто скопировали 0, но это совершенно другое. Этот ноль они вставили безо всякой причины, прямо «культ карго».

— Вы говорите, что если язык встроен в С, то логично сделать его синтаксис похожим на С. Но тогда должны быть С-программисты, которые хотят, чтобы код был на С, а не каком-то другом языке, который выглядит как С, но это не С. То есть вы не предполагали, что пользователи Lua будут ежедневно писать на С? Почему?

— Кто ежедневно пишет на С?

— Системные программисты.

— Именно. В этом и проблема, слишком много людей пишут на С, хотя им не следовало этого делать. Нужно сертифицировать программистов для использования С. Почему ПО такое плохое? Все эти хаки, наводнившие мир, все эти проблемы с безопасностью. Причина как минимум половины из них заключается в С. На этом языке в буквальном смысле трудно программировать.

— Но ведь Lua реализован на C.

— Да, и так мы узнали, насколько трудно программировать на С. Тут вам и переполнения буфера, и целочисленные переполнения, приводящие к переполнению буфера… Попробуйте написать программу на С, в которой не будет проблем с вычислениями, если пользователь введёт куда-нибудь любое число и всё будет проверено. А тут ещё проблемы с портированием: иногда на одном процессоре работает, а на другом… Безумие.

К примеру, совсем недавно у нас была проблема. Как узнать, не приводит ли ваша программа на С к переполнению стека? Я имеют в виду глубину стека, а не его переполнение из-за вашего вмешательства… Сколько вызовов можно сделать в программе на С?

— Зависит от размера стека.

— Верно. Что нам говорит документация? Если вы пишете на C и делаете функцию, которая вызывает другую функцию, которая вызывает другую функцию… то сколько вызовов можно сделать?

— 16 тысяч?

— Могу ошибаться, но в документации ничего об этом не говорится.

— Думаю, это потому, что всё зависит от размера.

— Конечно, это зависит от размера каждой функции. Они могут быть огромными, с массивами в аргументах, с автоматическим удалением в рамках функции… В документации ничего не сказано, и никак не проверить, является ли вызов корректным. Даже с трёхступенчатыми вызовами у вас может возникнуть проблема — программа корректная, но при этом падает. Корректная с точки зрения стандарта, но не корректная, потому что падает. Так что программировать на С очень сложно, поскольку здесь много нюансов… Ещё хороший пример: какой будет результат при вычитании двух указателей?

— Но C++ поддерживает разные типы.

— Нет, в C++ та же проблема.

— Какой тип у объявления? ptrdiff_t?

ptrdiff_t — это тип со знаком (signed type). Обычно, если слово занимает в памяти стандартный объём и в этом пространстве вычитаются два указателя, вы не можете представить все размеры с помощью типа со знаком. Что об этом говорится в документации?

Вычитая два указателя, если ответ помещается в diff указателя, тогда это будет ответом. Иначе вы получите неопределённое поведение. А как узнать, помещается ли ответ? Никак. Обычно, когда вы вычитаете указатели, вы должны быть готовы к проблемам. Хотя если вы индексируете элементы по 2 байта и больше, то индекс в знаковый тип поместится и всё будет хорошо.

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

Вот что я имею в виду, говоря о безопасной программе на С или С++.

— Вы рассматривали возможность реализации Lua на другом языке?

— Когда мы начинали, я рассматривал C++, но отказался от него из-за сложности, я не мог выучить весь язык. Возможно, полезно что-то позаимствовать из С++, но… даже сегодня я не вижу других кандидатов среди языков.

— Можете объяснить, почему?

— Потому что у меня нет альтернатив. Я могу лишь сказать, чем мне не нравятся другие языки. Я не хочу сказать, что С идеален или даже хорош, но это лучший вариант. Чтобы объяснить своё мнение, нужно сравнить его с другими языками.

— Что насчёт JVM?

— Ох, JVM. Да ладно, она не подходит для половины существующего железа… Портируемость — главный недостаток, но ещё и проблемы с производительностью. У JVM она выше, чем у .NET, но разница невелика. В JVM нельзя сделать многое из того, что делает Lua. К примеру, вы не можете управлять сборщиком мусора. Придётся использовать сборщик JVM, потому что нельзя поверх него реализовать другой сборщик. JVM потребляет кучу памяти. Когда запускается любая Java-программа, она сжирает около 10 Мб. И портируемость является проблемой не потому, что JVM не была портирована, а потому что её нельзя портировать.

— А что насчёт модификаций JVM, например, Mobile JVM?

— Это шутка, а не JVM. Какая-то микроверсия Java, а не Java.

— А другие статические языки, вроде Go или Oberon? Могут ли они стать основой для Lua, если бы вы сегодня создавали этот язык?

— Oberon… возможно… В Go есть свой сборщик мусора, а runtime-среда слишком велика для Lua. Oberon может быть кандидатом, но у этого языка есть большие странности. Например, почти полное отсутствие констант, если я не путаю. Ага, думаю, они перенесли const из Pascal в Oberon. У меня была книга об этом языке, он мне нравился. На нём была написана невероятная операционная система.
Помню, в 1994-м я видел демонстрации Oberon и Self. Знаете про Self? Это очень интересный динамический язык с JIT-компиляторами и прочим. Self был очень умным языком, авторы заимствовали ряд методик из мультипликации, чтобы замаскировать медлительность операций. Когда что-нибудь запускаешь, оно сначала — раз! — немного уменьшается, а потом раскрывается с какими-то эффектами. Реализовано было очень хорошо, они с помощью этих методик эмулировали движение…

А затем я посмотрел демонстрацию Oberon, который работал на железе, которое было примерно в 10 раз слабее железа для Self, очень старый компьютер. В Oberon кликаешь, и всё немедленно работает, система была очень быстрой.

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

— Haskell?

— Я не знаю Haskell, и не знаю, как на нём реализовать Lua.

— А как вы относитесь к Python, R или Julia с точки зрения основы для новой реализации Lua?

— Думаю, у каждого из этих языков есть своя ниша.

R хорош для статистики. Он очень сильно завязан на эту сферу, и в этом его сила.

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

Мы используем Python на наших курсах, учим основам программирования, показываем циклы и целые числа. Все сначала довольны, а потом просят сделать графическое приложение, и нам требуется какая-нибудь графическая библиотека. А почти со всеми графическими библиотеками вы получаете API… Ну, я плохо знаю Python, это очень продвинутый язык. Он создаёт иллюзию лёгкости использования и предлагает библиотеки для любых нужд, но тут либо просто, либо «всё включено».

И когда начинаешь писать на этом языке, пошло-поехало: ой, нужно изучить ООП, наследования, ещё что-то. Кучу библиотек. Такое чувство, что авторы с гордостью демонстрируют в API более продвинутые возможности языка, просто чтобы что-нибудь показать. Вызовы функций, стандартные типы и так далее. У вас есть такой объект, а если вы хотите другое, то вам нужно создать ещё один объект…

Вы можете сделать что-то простое, но обычно это не стандартный поиск по шаблону (pattern matching). Вы сопоставляете, объект возвращает результат, применительно к нему вы вызываете метод, чтобы получить настоящий результат сопоставления. Иногда есть способ проще, но он не очевиден, многие им не пользуются.

Другой пример: я преподавал курс о поиске по шаблону и хотел использовать синтаксис наподобие Perl. Я не мог использовать Lua, потому что у него совершенно иной синтаксис. Думал, Python отлично подойдёт. Но в этом языке есть лишь несколько прямых функций для базовых вещей, а для всего посложнее нужно знать объекты, методы и прочее. Мне же нужно было просто что-то сделать и получить результат.

— И чем же вы в конце концов воспользовались?

— В тот раз я объяснял на примере Python. Но даже Perl гораздо проще: вы сопоставляете и получаете результат в виде $1, $2, $3. Однако у меня не хватает смелости использовать Perl, так что…

— Я два года писал на Python, пока не заметил, что в нём есть декораторы (замечание Ярослава Дынникова из команды Tarantool).

— Ага, и когда хочешь использовать библиотеку, то нужно изучить её, а ты не понимаешь API, вот это всё. Python создаёт иллюзию простоты, но он весьма сложен.

Я мало знаю про Julia, но она напоминает мне про LuaJIT, с той точки зрения, что иногда выглядит как предмет гордости программиста. Вы можете получить прекрасные результаты, но нужно очень хорошо понимать, что там происходит. Не будет так, что вы пишете код и получаете хороший результат. Нет, вы пишете код, иногда результат хороший, а иногда ужасный. И во втором случае у вас под рукой много хороших инструментов, отображающих сгенерированный код на промежуточном языке, почти ассемблере. Вы просматриваете весь код и понимаете: а, вот это не оптимизировано из-за этого. Это проблема программистов, они любят играть, и иногда любят что-то за сложность, а не за простоту.

Я мало знаю про Julia, но однажды слушал выступление об этом языке: смотрите, какой классный язык, мы написали программу и она идеальна. Не помню точно, там что-то было про матричное умножение. А вот идеальные числа с плавающей запятой, а вот идеальные double, а потом мы вставили комплексные [числа]… это была трагедия. Только в сто раз медленнее.

(саркастически) «Смотрите, как классно, у нас есть инструмент, мы можем видеть весь ассемблер, а затем пойти и поменять это, это и это. Смотрите, как эффективно». Ага, вижу, я могу писать прямо на ассемблере.

Но это лишь одно выступление. Я мало изучал R, и немного писал на Python.

— Что думаете про Erlang?

— Это забавный язык. У него есть ряд очень удачных применений, интересно реализована отказоустойчивость. Но авторы утверждают, что это функциональный язык, однако такие языки не должны иметь состояний, в том-то и идея. А у Erlang есть огромное скрытое состояние сообщений, которые отправлены, но ещё не получены. Поэтому каждый небольшой процесс полностью функциональный, а программа целиком абсолютно не функциональная.

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

— Значит, Erlang врёт про свою функциональность, а Python врёт про свою простоту. О чём тогда врёт Lua?

— О том, что она мала. Lua всё ещё меньше большинства языков, но если вам нужен действительно маленький язык, то Lua окажется больше, чем вы ожидаете.

— А какой язык тогда маленький?

— Forth, он мне нравится.

— В нём есть место для уменьшенной версии Lua?

— Может быть, но это будет трудно. Мне нравятся таблицы, но они не так уж малы. Если хотите представить что-нибудь маленькое, то таблицы вам не подойдут. Синтаксис будет как в Lua, мы будем называть это Lua, но это будет уже другой язык.

Это будет как микроверсия Java. Можете называть её Java, но разве в ней есть многопоточность? Нет. Есть рефлексия (reflection)? Нет. Так зачем её использовать? У неё синтаксис Java, такая же система типов, но это совсем не Java. Это другой язык, он проще в изучении, если вы уже знаете Java, но это не она сама.

Если хотите сделать маленький язык, который выглядит как Lua, только без таблиц он не… Возможно, ради миниатюризации придётся объявлять таблицы, словно FFI.

— Существуют ли уменьшенные адаптации Lua?

— Наверное, не знаю.

— Готов ли Lua к чисто функциональному программированию? Вы можете это с ним сделать?

— Конечно, вы можете так программировать. Это не особенно эффективно, в этом преуспел только Haskell. Если начнёте использовать монады и подобные конструкции, создавать новые функции, компоновать их… Вы можете это делать в Lua, будет работать, но чтобы получилось хорошо, придётся реализовать методики, отличные от тех, что применяются в нормальных императивных языках.

— Кстати, есть библиотека для обеспечения функционального программирования на Lua.

— Да, она здравая и рабочая, если вам нужна производительность. С её помощью многое можно сделать. Мне нравится функциональное программирование, я постоянно его практикую.

— Хочу спросить о сборщике мусора, потому что у нас только изменяемые объекты и нужно эффективно их использовать. Lua для этого хорошо подходит?

— Думаю, новая инкарнация сборщика будет полезна, но повторюсь…

— «Молодые умирают молодыми»? Сборщик, который работает с молодыми объектами?

— Да, верно. Хотя, как я уже говорил, даже со стандартным сборщиком производительность будет не оптимальной, но допустимой. Для большинства действий вам даже не понадобится высокая производительность, если вы пишете серверы или выполняете большие операции.

— Какие задачи из функционального программирования вы делаете на Lua?

— Простой пример. Я пишу книгу в своём формате, и у меня есть форматировщик, который преобразует текст в LaTex или DocBook. Он полностью функциональный, в нём выполняется большой поиск по шаблону… Он слегка похож на LaTex, но гораздо унифицированней. Вместо обратного слеша используется @, имя макроса и один аргумент в фигурных скобках. Gsub распознаёт такие данные, а затем вызывает функцию, которая что-то делает и что-то возвращает. Инструмент полностью соответствует парадигме функционального программирования, в нём лишь функции поверх функций, поверх функций, и последняя функция выдаёт большой результат.

— Почему вы не программируете на LaTeX?

— Чистом LaTeX? Во-первых, если материала много, то получается слишком сложно и трудно. Есть несколько вещей, которые я не знаю, как реализовать в LaTex. Например, хочу вставить в текст кусок inline-кода. Ещё там есть стандартный оператор /, который даёт фиксированный пробел. А пробелы — это всегда неправильно. Все настоящие пробелы переменные, это зависит от выравнивания строки, которая расширяется в одних местах и сжимается в других, в зависимости от разных критериев. А фиксированные пробелы иногда выглядят слишком большими, иногда слишком маленькими. Это тоже зависит от того, как вы пишете код.

— И всё же вы преобразуете свой формат в LaTeX?

— Да, но после большой предварительно

© Habrahabr.ru