Русский
Русский
English
Статистика
Реклама

Interpreter

Umka. Жизнь статической типизации в скриптовом языке

21.06.2020 14:07:34 | Автор: admin


В своё время посты на Хабре и Reddit о статически типизированном скриптовом языке Umka вызвали весьма активную дискуссию.

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

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

Информация о типах во время исполнения программы (RTTI). Проект начинался с радикального отказа от хранения типов данных при исполнении программы. Вся информация о типах терялась после компиляции в байт-код виртуальной машины. В принципе, статическая типизация позволяет это сделать, а заодно избавляет от странных трюков вроде упаковки данных в NaN, к которой, например, прибегают создатели JavaScript и Wren ради увеличения быстродействия. Однако обнаружились два случая, в которых пришлось использовать RTTI:

  • Приведение интерфейсного типа данных к конкретному прямой аналог утверждения типа (type assertion) в Go, а также, отчасти, оператора dynamic_cast в C++. Оно требуется и при сборке мусора, содержащегося в данных, приведённых к интерфейсному типу.
  • Сборка мусора, связанного с динамическими структурами данных вроде списков и деревьев.

Быстродействие. Изначально Umka никак не предназначался для установления рекордов быстродействия. Безразличие публики к медлительности Python наводило на мысль, что скорость вовсе не то качество, которого в первую очередь ожидают от скриптового языка. Однако успех LuaJIT и активная реклама Wren заставили задуматься. После этого меня уже не удивляло, что и ранние публикации про Umka вызвали вопросы о быстродействии, хотя мне по-прежнему интересно, от кого в первую очередь исходит спрос на скорость. От разработчиков игр?

Пока полный набор тестов не готов, я могу поделиться лишь предварительными результатами замеров. В численных задачах (например, задаче многих тел) Umka надёжно опережает Python, а если в задаче активно используется цикл for, то Umka даёт выигрыш даже по сравнению с Wren, который позиционируется автором чуть ли не как самый быстрый скриптовый язык после LuaJIT. Наглядным примером служит перемножение больших матриц:


Умножение матриц 400 x 400 (AMD A4-3300M @ 1.9 GHz, Windows 7)

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

Задачи с интенсивной сборкой мусора (например, создание и обход двоичных деревьев) вызывают много сомнений по поводу эквивалентности сравниваемых алгоритмов. Например, известная реализация двоичных деревьев на Python возвращает содержимое узлов россыпью и выглядит так, будто в принципе допускает размещение всего дерева на стеке вообще без использования кучи и сборки мусора. Однако она, по-видимому, требует динамической типизации и не может быть точно воспроизведена на Umka. Если же потребовать возвращать узлы в виде структур, как в Umka (а за неимением структур приходится требовать объекты), то быстродействие Python сразу же падает в 3-4 раза. Вариант на Umka вдвое отстаёт от первой реализации и вдвое опережает вторую. Какое сравнение корректнее не знаю.

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


Пример трёхмерной сцены, содержимое которой задаётся скриптом на Umka

Обобщённые типы и функции (generics). Как только читатель улавливает сходство Umka с Go, пускай даже синтаксическое следует вопрос о поддержке generic'ов. Работа в этом направлении пока не вышла из стадии обзора подходов. Конечно, хотелось бы воспользоваться предложениями разработчиков Go, однако сосуществование в их головах интерфейсов и контрактов всегда отпугивало, как странное дублирование понятий. К удивлению и радости, в только что вышедшей новой редакции черновика контракты исчезли по тем же причинам, о которых размышлял и я. Пока generic'ов в Umka нет, остаётся пользоваться, как и в Go, пустыми интерфейсами interface{}.

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

Простой интерпретатор Lisp на Umka

26.09.2020 22:13:23 | Автор: admin

Разработка моего статически типизированного скриптового языка Umka вошла в ту стадию, когда потребовалась проверка языковых возможностей на более сложных примерах, чем скрипты в пару десятков строк. Для этого я решил реализовать на своём языке интерпретатор Lisp. На это меня вдохновил педагогический эксперимент Роба Пайка, одного из создателей языка Go. Недавно Пайк опубликовал маленький интерпретатор Lisp на Go. Особенно впечатлило замечание Пайка, что описание интерпретатора заключено на одной странице 13 древнего руководства по Lisp 1.5. Учитывая синтаксическое родство Umka и Go, было трудно не поддаться соблазну построить такой интерпретатор на Umka, но не буквальным переносом кода Пайка, а полностью заново, от основ. Надеюсь, знатоки Lisp и функциональных языков простят мне наивное изумление от соприкосновения с прекрасным.

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

Определение минимального интерпретатора Lisp действительно занимает меньше страницы. Конечно, с некоторой натяжкой: в нём используются функции, определённые на нескольких предыдущих страницах. Кажется, создатель Lisp Джон Маккарти из азарта старался превзойти сам себя в лаконизме и в итоге опубликовал микроруководство по Lisp, содержащее определение языка вместе с исходником интерпретатора в общей сложности две журнальные страницы. Правда, добавил в заголовок: "Not the whole truth".

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

Базовые конструкции языка для тех, кто с ними не знаком
  • (car x) выделение головы списка x

  • (cdr x) выделение хвоста списка x

  • (cons x y) соединение списков x и y

  • (atom x) проверка x на атомарность

  • (eq x y) проверка атомарных элементов x и y на равенство

  • (cond (a x) (b y)) выбор значения x или y по условию a или b

  • (quote x) указание использовать x как есть, без вычисления

  • ((lambda (x) a) y) вызов безымянной функции с телом a, формальным параметром x и фактическим параметром y

  • ((label ff (lambda (x) a)) y) присвоение безымянной функции имени ff

  • t истина

  • nil ложь или пустое выражение

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

((label fac (lambda (n) (cond ((eq n 0) 1) ((quote t) (mul n (fac (sub n 1))))))) 6)

В микроруководстве Маккарти этими средствами выражен весь интерпретатор Lisp, за исключением лексического и синтаксического разбора. В руководстве Lisp 1.5 на той самой странице 13 приведён почти такой же интерпретатор, но в более человекочитаемом псевдокоде. Его я и взял за основу своего маленького проекта. Потребовалось лишь добавить разбор текста программы, некое подобие REPL и импровизированную арифметику. Роб Пайк, видимо, поступил так же, но отказался от конструкции label в пользу defn, которая позволила ему не определять функцию заново всякий раз, когда требуется её вызвать. В ядре Lisp такой возможности не предусмотрено.

Поскольку весь интерпретатор пронизан рекурсией и обработкой деревьев, он послужил отличным тестом многих возможностей языка Umka от формирования стековых кадров до сборки мусора. Думаю, Umka хорошо справился с испытанием. Исправлять пришлось лишь два-три мелких бага, связанных с экспортом имён и опережающим описанием типов. Весь код интерпретатора занял меньше 400 строк.

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

Подробнее..

Категории

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

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru