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

 

Язык будущего

Однажды я прочитал, что за всю человеческую историю письменность изобрели всего два или три раза. Все остальное — это производные того, что тысячи лет назад придумали в Шумере, Китае и, возможно, в долине Инда. Если это в самом деле так, то шумерам пора подвинуться. Не так давно появилась четвертая письменность, превосходящая предшественников по всем статьям. Речь, конечно, об эмодзи.

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

У людей, связанных с компьютерами, эмодзи должны вызывать особенно живой интерес. Эксперты давно предрекают, что обыкновенные ПК будут вытеснены смартфонами и планшетами. И что тогда станут делать программисты? Мучительно вводить при помощи экранной клавиатуры зарытые на третьем уровне фигурные скобки? Готовиться можно начинать прямо сейчас, и один из способов — полностью избавиться от печатных команд и использовать вместо них эмодзи.

Гуглу известны по меньшей мере три языка программирования, создатели которых заменили устаревшие буквы и цифры на эмодзи: Emojicode, Emojilisp и ?. Emojilisp скучноват. У клевера нет документации. Выбор очевиден — учим эмодзикод!

 

Твой первый фрукт

Первая программа, которую пишут на любом языке программирования, — это Hello World. Не будем изобретать велосипед и последуем традиции. Вот как Hello World выглядит на эмодзикоде.

Попробуем разобраться, что мы видим.

Клетчатый флажок ? (:checkered_flag:) — это обозначение метода, с которого интерпретатор начнет исполнение программы. Его можно сравнить с методом main в Java. В эмодзикоде, как и в Java, не бывает неприкаянных функций, и к клетчатому флажку это относится в полной мере. В ранних версиях языка программисту полагалось явно декларировать класс, к которому относится этот метод, но потом синтаксис упростили. Этот класс по-прежнему есть, но теперь его автоматически добавляет сам компилятор.

Ухмылка ? (:grinning:) — это метод объекта строка, который распечатывает ее содержимое. Для вызова метода сначала указывают его эмодзи, а затем объект, к которому происходит обращение. Им в данном случае служит литерал строки «Hello World!». Он окружен квадратиками с буквами abc: в эмодзикоде они заменяют кавычки.

И наконец, виноград и арбуз — две главные ягоды эмодзикода. В этом языке они играют ту же роль, что и фигурные скобки в си или JavaScript или ключевые слова begin и end в Pascal. Любой блок кода должен быть заключен между ними. В начале — виноград ? (:grapes:). В конце — арбуз ? (:watermelon:).

При первом столкновении с эмодзикодом хочется понять принцип, по которому создатель языка отбирал эмодзи. Не стоит — это не пойдет на пользу психике. Примерно с той же проблемой сталкиваются люди, которые изучают иероглифическую письменность. Они узнают, что, например, японский иероглиф 鮮, означающий свежесть, состоит из рыбы и овцы, а стыд (恥) — из уха и сердца. Какая связь между стыдом и ухом? При чем тут рыба и овца? Это невозможно понять — только запомнить.

Так и с эмодзикодом. Запоминай: виноград и арбуз. Смысла нет.

Компиляция и исполнение

Для начала нужно скачать сам эмодзикод с его страницы на GitHub. Выбирай самую свежую версию: в новых бетах меньше ошибок, чем в старых релизах.

Исходные файлы на эмодзикоде имеют расширение .emojic. Перед запуском их нужно скомпилировать.

emojicodec hello.emojic

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

emojicode hello.emojib

И hello.emojic, и все прочие программы из этой статьи можно взять отсюда.

 

Медведь с пирогом и двумя пистолетами

Написав Hello World, понимаешь: будущее пока не настало. Мы всё еще программируем при помощи компьютеров, которые совершенно не приспособлены для ввода эмодзи. Это серьезная проблема. Если мы хотим чего-то достичь, нам потребуется программа, которая позволит писать программы на эмодзикоде без самих эмодзи.

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

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

Желтый старичок ? (:older_man:) отмечает комментарии. Встретив его, компилятор сразу же переходит к следующей строчке. Многострочные комментарии удобнее делать при помощи старушек ? (:older_woman:). Все, что попадает между двух старушек, будет проигнорировано.

Дальше идет главное: декларация класса.

Она всегда начинается с едва различимого на белом фоне белого кролика ? (:rabbit2:). За ним следует эмодзи класса. В нашем случае это подсолнух ? (:sunflower:), но можно было бы выбрать и другой значок. И наконец, виноград, значение которого мы уже разбирали. Весь код от него и до ближайшего арбуза относится к подсолнуху.

Пирог ? (:cake:) служит для объявления переменных.

После пирога необходимо указать название и класс переменной. Название может состоять из любых символов, кроме пробелов и эмодзи; русские буквы тоже разрешены. Что касается класса, то наши переменные относятся к классу abcd ? (:abcd:), то есть строки. Этот класс определен в стандартной библиотеке эмодзикода.

В эмодзикоде к переменным объекта может обращаться только его собственный код. Чтобы их значения можно было узнавать или менять из других мест, требуются модифицирующие методы: геттеры) и сеттеры. У подсолнуха таких метода два: цветок ? (:blossom:) сообщает значение переменной эмодзи, а этикетка ? (:label:) — значение названия.

Свинья ? (:pig2:) в начале строки — это признак декларации метода. В примере рядом с ней находится цветок ? (:blossom:). Этот эмодзи будет использоваться в качестве названия метода. Его, как и эмодзи класса, выбирает программист.

После названия метода могут быть объявлены его аргументы и их типы. Цветок из примера не требует аргументов, поэтому их тут нет, но он возвращает значение. Тип возвращаемого значения необходимо сообщить после стрелки вправо ➡ (:arrow_right:). В нашем случае это строка ? (:abcd:).

Между виноградом и арбузом заключен весь код метода. Код цветка из примера — это единственная строчка, которая при помощи яблока возвращает значение переменной объекта с названием эмодзи (мы определили ее выше). Яблоко ? (:apple:) в эмодзикоде соответствует ключевому слову return в других языках.

При создании экземпляра класса все переменные объекта должны быть инициализированы, иначе компилятор сообщит об ошибке. Для этого служит особая разновидность метода — инициализатор. Декларация инициализатора начинается с кота ? (:cat2:), а не со свиньи, как у обычного метода.

Инициализатор с именем капля ? (:droplet:) принимает два аргумента: @эмодзи и @название. Типы аргументов указаны сразу после их имен (в примере оба аргумента — строки). Обычные инициализаторы не возвращают значений, поэтому стрелки вправо в декларации капли нет.

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

Заварной крем ? (:custard:) нужен для того, чтобы присваивать переменным новые значения — примерно как ключевое слово let в бейсике. За кремом идет название изменяемой переменной. Она приравнивается к выражению, которое занимает остаток строчки. В примере переменной эмодзи присваивается значение аргумента @эмодзи, а переменной название — аргумента @название.

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

В коде присутствует пара незнакомых эмодзи: печенье ? (:cookie:) и красный крестик ❌ (:x:). Красный крестик в литералах строк — это аналог обратной дроби в других языках. Вместо \n (перевод строки) или \t (табуляция) в эмодзикоде пишут :x:n или :x:t. Печенье же можно сравнить с оператором . в PHP. Оно тоже служит для объединения строк. Принцип действия, впрочем, иной: два печенья ставят по краям списка складываемых строк, а не между его элементами, как точки в PHP.

Пока все просто. Теперь добавим класс, который будет заниматься кое-чем посложнее: загружать библиотеку эмодзи из текстового файла, где эмодзи и их названия сохранены в виде списка значений, разделенных табуляцией (tab separated values).

Вот код этого класса.

В классе книги ? (:books:) объявлена только одна переменная — список. Она относится к классу десерт ? (:ice_cream:), который служит для хранения списков — стандартной коллекции объектов, к элементам которой можно обращаться по их порядковому номеру. В эмодзикоде размер списка может изменяться во время работы программы.

Десерт — это обобщенный класс, или дженерик (generic). Его можно использовать для того, чтобы создать список объектов любого типа. Однако тип элементов должен быть указан при декларации переменной. Для этого после эмодзи десерта ставят ракушку ? (:shell:), а затем эмодзи класса элементов.

Переменная список — это список подсолнухов, поэтому после десерта и ракушки идет подсолнух. А если бы нам понадобился, например, список строк, то описание его типа состояло бы из десерта, ракушки и abcd.

В инициализаторе книг мы первым делом создаем новый список подсолнухов и сохраняем его в словаре. Новые объекты создаются при помощи синего ромба ? (:large_blue_diamond:), после которого указан сначала класс нового объекта, а затем эмодзи инициализатора. Нас интересует лягушка ? (:frog:) — это инициализатор десерта, который создает пустой список.

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

Начнем с мороженого. Как и крем, мороженое служит для присваивания. Переменные, которые инициализированы мороженым, «замерзают» и не могут быть изменены. Инструкция к языку рекомендует по возможности отдавать предпочтение мороженому, а не крему. В этом случае некоторые ошибки всплывут еще на стадии компиляции (по крайней мере, в теории). Компилятор следит за соблюдением этого правила и выдает предупреждения, когда замечает необновляемые переменные без мороженого.

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

Выражения удобно разбирать справа налево. Например, выражение, которое будет присвоено исходнику, делится на две основные части. Сначала страница ? (:page_facing_up: — стандартный класс для работы с файлами) загружает двоичные данные. Затем двоичные данные при помощи метода abcd преобразуются в строку.

Файл загружается при помощи классового метода картотека ? (:card_index:), который относится к классу страница. Классовые методы отличаются от обычных тем, что их можно вызывать прямо из класса, даже не создавая объекта. Чтобы это сделать, нужен пончик ? (:doughnut:). Картотека принимает на входе один аргумент (строку @имя-файла) и возвращает двоичные данные. Но не просто так, а в конфетной обертке.

Конфеты — это, пожалуй, самая любопытная и в то же время самая раздражающая часть эмодзикода. Во многих языках принято сообщать об ошибках, возвращая невозможные значения — например, NULL в си или None в Python. Программист, вызвавший функцию, должен проверить ее результат и убедиться в том, что ошибки не было. В эмодзикоде эта практика формализована, и компилятор внимательно следит, чтобы ни один сомнительный результат не остался непроверенным. Для этого и нужны конфеты.

В эмодзикоде в случае ошибки методы возвращают так называемое ничто — уникальный объект, генерируемый при помощи молнии ⚡ (:zap:). Чтобы компилятор не жаловался на несовпадение типов, при декларации метода к типу результата прилагают конфету ? (:candy:). Про такие значения говорят, что они в конфетной обертке. Внутри обертки может оказаться либо обещанный результат, либо ничто. Пока не развернешь, не узнаешь.

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

В нашем случае использован более простой способ — пиво ? (:beer:). Пиво разворачивает конфету и, если внутри ничего нет, останавливает программу с сообщением о фатальной ошибке. Другими словами, оно представляет собой нечто вроде оператора преобразования типа со встроенным assert. Очевидно, что пиво годится в двух случаях: когда правильность результата не вызывает сомнений и когда фатальные ошибки не пугают.

Метод продолжается циклом ? (:repeat_one:), который перебирает элементы списка. В данном случае списка строк, созданного методом пистолет переменной исходник. Пистолет разделяет строку на подстроки в тех местах, где встречается аргумент. Мы передали ему литерал с переводом строки, поэтому пистолет разбил исходник на отдельные строчки. На каждом шаге переменная цикла строка принимает значения всех элементов перебираемого списка по очереди.

Мандарин ? (:tangerine:) — это условный оператор, который вызывает следующий за ним блок кода, когда выражение равно истине. Аналог else, который срабатывает в обратном случае, в эмодзикоде тоже есть — это клубника ? (:strawberry:). В примере мандарин измеряет длину списка курицей ? (:chicken:) и проверяет, чтобы в нем было больше одного элемента.

Проверка нужна для того, чтобы безопасно извлечь свиным пятачком ? (:pig_nose:) элементы из списка разбитая-строка. Нулевой элемент оказывается сохранен в переменной эмодзи, а первый — в названии. Остается создать из полученных данных новый подсолнух, после чего медведь ? (:bear:) добавит его в список, который мы определили раньше.

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

Эмодзикод позволяет добавлять новые методы или переменные в уже существующие классы, в том числе и в те, которые входят в стандартную библиотеку. Воспользуемся этой возможностью, чтобы расширить стандартные строки методом палитра ? (:artist_palette:). Он возвращает новую строку, в которой все подстроки, совпадающие с аргументом @было, заменены на значение аргумента @стало.

Расширение уже существующего класса очень похоже на декларацию нового, только вместо кролика используется кит ? (:whale2:). Обрати внимание на собаку ? (:dog2:) в коде добавленного метода — это ссылка на текущий объект, аналогичная this или self в других языках. Мы используем пистолет, чтобы разделить собаку (текущую строку) на части по значению @было, а затем передаем получившийся список и значение @стало инициализатору строк десерт. Десерт собирает из его элементов новую строку, ставя между ними @стало, подобно join в Python.

Последний рывок. Во-первых, дополняем подсолнух методом шестеренка ⚙ (:gear:), который выдает короткое название эмодзи в распространенном формате, поддерживаемом, среди прочего, в GitHub, YouTube и Slack: двоеточия по краям и прочерки вместо пробелов.

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

Код клетчатого флажка, который использует эти классы для выполнения реальной работы, можно и пропустить: он прост и очевиден. На основе файла emoji.txt создается экземпляр книг. Своей палитрой он перерабатывает содержимое файлов, названия которых перечислены в командной строке. Результат сохраняется в файлах с расширением .emojic и отдается на рассмотрение компилятору.

Подробности — в файлах codes.emojic и emo.emojic, которые прилагаются к статье.

emo.emojic и его текстовый аналог, в котором вместо эмодзи указаны их короткие названия
emo.emojic и его текстовый аналог, в котором вместо эмодзи указаны их короткие названия
 

Протокол на велосипедиста

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

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

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

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

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

В стандартной библиотеке присутствуют два протокола, связанных с итерацией. Уже известный нам цикл :repeat_one: требует, чтобы перебираемый объект поддерживал протокол цикла. Стандартные списки, которые мы перебирали, его поддерживают, но такой протокол может реализовать и любой другой класс, в том числе самодельный. Тогда и его можно будет перебирать при помощи цикла.

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

В начале подключим описания классов подсолнух и словарь, сохраненные в файле codes.emojic. Его загружает оператор свиток ? (:scroll:), действующий по тому же принципу, что и include в си.

Класса велосипедист, который мы создаем этим методом, пока не существует, но сейчас мы его определим.

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

Согласно документации, протокол цикла требует, чтобы в классе был метод данго ? (:dango: — что-то вроде шашлыка из рисовых шариков). Этот метод должен возвращать итератор, то есть объект класса, удовлетворяющего другому стандартному протоколу — данго.

В велосипедисте мы сохраняем ссылку на словарь и слово для фильтрации (это происходит в инициализаторе колокольчик). Обещанный протоколом метод данго создает объект класса велосипед и передает ему эти данные. Велосипед — это наш итератор. Сейчас мы им займемся.

Крокодил подсказывает нам, что велосипед обязуется соответствовать протоколу данго с элементами типа подсолнух. Для этого классу необходимы два метода: стрелка вниз ? (:arrow_down_small:), которая возвращает один элемент и переходит к следующему, и вопросительный знак ❓ (:question:), проверяющий, не пора ли заканчивать перебор.

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

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

Бегун дает нам возможность воочию увидеть несколько конструкций эмодзикода, которые мы пока что не встречали: условное присваивание, проверку «ничего» и еще одну разновидность цикла — :repeat: (такой цикл повторяется до тех пор, пока выражение истинно).

Условное присваивание, как и обычное условие, начинается с мандарина, но вместо выражения за ним следует оператор присвоения — мороженое.

Вот что происходит в этой строчке. Сначала мы свиным пятачком вытаскиваем из списка элемент с порядковым номером индекс. Пятачок не уверен, что в списке действительно есть элемент с таким номером, и потому возвращает его в обертке. Это позволяет ему выдать «ничто», если индекс окажется неверным. Мандарин, получив конфету, разворачивает ее и проверяет значение. Если в обертке что-то есть, он сохраняет результат в переменной с названием эмодзи, а затем запускает прилагающийся блок кода.

Следующий мандарин — это не условное присваивание, а обычное условие, но оно тоже имеет дело с пустотой. Облако ☁ (:cloud:) разворачивает конфеты и проверяет, что внутри. В отличие от пива, оно не передает извлеченное значение дальше, а возвращает истину, если в обертке было ничто. В данном случае облако проверяет, нашла ли лупа ? (:mag:) слово в ярлыке эмодзи. Когда найти не удается, лупа возвращает ничто. Это вызывает срабатывание облака, но его значение инвертирует зеленый крестик ❎ (:negative_squared_cross_mark:). В итоге условие выполняется только в том случае, если лупа что-то нашла. Это значит, что бегун достиг цели. Яблоко с молнией ⚡ (:zap:) помогают нам немедленно покинуть метод.

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

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

Целиком исходный код утилиты можно найти в файле find.emojic. Если ты его освоил, то знаешь об эмодзикоде почти все. Кроме, возможно, замыканий. Но без замыканий вполне можно обойтись. Я советую поступить именно так.

Компиляция и применение утилиты find.emojib для поиска эмодзи
Компиляция и применение утилиты find.emojib для поиска эмодзи
 

Brainfuck и тайна эмодзикода

Наш коронный номер — интерпретатор легендарного языка программирования Brainfuck, написанный на эмодзикоде. Это неплохой бенчмарк. Сравнив скорость работы нашего интерпретатора с вариантами Brainfuck на других языках, мы сможем оценить производительность эмодзикода. Быстрее ли он, чем Python? Чем Java? Чем си, наконец?

Brainfuck известен своей примитивностью. В нем нет ни объектов, ни функций, ни переменных. Весь язык состоит из восьми простейших команд и одного указателя. Команды + и - увеличивают или уменьшают содержимое ячейки памяти, которая находится по лежащему в указателе адресу. Чтобы сдвинуть указатель на одну ячейку в ту или другую сторону, служат команды < и >. Точка и запятая записывают или считывают один символ из стандартных потоков ввода-вывода. Две оставшиеся команды, [ и ], заменяют циклы и условные переходы. Заключенный между ними блок кода повторяется до тех пор, пока значение ячейки памяти по указателю не станет равно нулю.

Такая простота — это сразу и плюс и минус. С одной стороны, нам же меньше работы. Основной цикл интерпретации Brainfuck нетрудно уместить в два десятка строк эмодзикода. Все, что требуется, — перебирать символы программы и выполнять соответствующие им команды. С другой стороны, программирование на таком языке — это сущий ад. Посуди сам — вот версия Hello World на Brainfuck.

++++++++[>+++++++++<-]>.<+++++[>++++++<-]>-.+++++++..+++.<
++++++++[>>++++<<-]>>.<<++++[>------<-]>.<++++[>++++++<-]>
.+++.------.--------.>+.

К свиньям, винограду и мандаринам эмодзикода так или иначе, но можно привыкнуть. Привыкнуть к Brainfuck нельзя. К счастью, нам это и не нужно. Мы воспользуемся тем, что сочинили другие. Чаще всего производительность интерпретаторов тестируют при помощи программы mandel.b: это почти 12 тысяч команд, которые рисуют самый известный фрактал — множество Мандельброта.

Программа mandel.b, написанная на Brainfuck, рисует фрактал, но делает это очень-очень медленно
Программа mandel.b, написанная на Brainfuck, рисует фрактал, но делает это очень-очень медленно

Даже с таким простым языком лобовой подход не очень эффективен. Простенький интерпретатор Brainfuck на си, найденный в интернете (bfi.c), тратит на mandel.b почти десять минут. Перевод на эмодзи замедлил его до такой степени, что за два часа он закончил не больше четверти фрактала. Это не производительность, а слёзы.

Нужно действовать умнее и перед запуском программы составить таблицу соответствий между открывающими и закрывающими скобками ([ и ]). Тогда не придется искать точку перехода всякий раз, когда исполнение доходит до одной из них. Попутно можно пересчитать повторяющиеся инкременты и декременты (+, -, < и >), чтобы потом исполнять их за один шаг.

Команды эмодзикода поместим в список code, а соответствующие им данные (точки перехода для скобок и количество повторов для инкремента и декремента) — в список args. Вот как выглядит получившийся цикл интерпретации Brainfuck на эмодзикоде. Справа для понятности приведен соответствующий код на Python.

Список tape изображает область памяти Brainfuck, адресуемую указателем xc. Текущую позицию в программе (то есть в списках code и args) хранит переменная pc.

Это помогло: интерпретатор Brainfuck заработал в несколько раз быстрее и победил Мандельброта за 74 минуты (со свертыванием повторяющихся операций — 38 минут). Это быстрее, чем идентичный код на Python, для которого решение этой задачи растягивается на 144 минуты (со свертыванием — 47 минут).

Если производительности интерпретатора не хватает, нужен компилятор: скрипт, который пройдет по программе на Brainfuck и для каждой команды выдаст соответствующую строчку кода на интересующем нас языке — хоть на си, хоть на Python, хоть на Emojicode. Получившиеся исходники можно скормить соответствующим языкам, и пусть они разбираются.

Результат оказался таким удачным, что, возможно, с этого следовало начинать. Mandel.b, перекомпилированный в си, нарисовал множество Мандельброта всего за 29 секунд (6,9 секунды со свертыванием). Java и JavaScript примерно в пять раз медленнее: 90 (36) секунд для Java и 119 (47) для JavaScript. Эмодзикод пришел к финишу почти наравне с Ruby: 338 (165) секунд у Ruby и 371 (268) у Emojicode. Python отработал свое в три раза медленнее, чем Emojicode: 1129,9 (401) секунды.

Любопытно, что при сравнении с другим бенчмарком, программой bench.b, разрыв между Python и Emojicode не так велик: 855,4 против 864,7 секунды. Разница, судя по всему, в том, что в mandel.b лишь около 20% шагов исполнения — это операции с ячейками памяти (список tape). В bench.b на них приходится почти 50% шагов. Из этого можно сделать вывод, что работа со списками — это слабое место эмодзикода, существенно уменьшающая его производительность.

 

Ошибки эмодзикода

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

Одна из ошибок эмодзикода попалась при портировании Brainfuck. И какая! Для нейтрализации таких ошибок, если верить комиксу XKCD, весь проект лучше уничтожить огнем. Она касалась словарей — стандартной коллекции эмодзикода, где ключами служат строки, а не порядковые номера, как в списках. Оказалось, что в определенных условиях при сохранении в словарь значения с одним ключом меняются значения сразу нескольких элементов с другими ключами. Именно поэтому в статье упоминаются списки, а о словарях ни слова.

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

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

Конец уже близок, а мы так и не получили ответов на многие вопросы. Зачем автор эмодзикода это делает? По какой невообразимой причине растекаются значения в словарях? Почему медведь? Несмотря ни на что, знакомство с этим языком программирования нельзя назвать совсем уж неприятным (стокгольмский синдром — сильная вещь). Он почти забавен. Но стоит ли упоминать его в резюме? Еще один вопрос, и в такой неподходящий момент.

Но есть и ответы. Вот главный: я наконец понял, чем должна кончаться эта статья.

Арбуз.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    3 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии