Programming
Perfect code
Compilers
August 2017 29

Нестандартный подход к построению современного языка программирования

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

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

В современных языках программирования что-то не так


Чтобы не было скучно, речь пойдёт именно о недостающих тонкостях языков, а не о недостатках существующих инструментов (зачастую язык сильно связан с конкретной средой разработки). Итак, ниже я отобрал 5 самых «ненужных и бесполезных» пунктов, о которых «никто не говорит», а я расскажу. Да, автор человек и любит холивары в комментариях, но просит воздержаться.

1. Нормальные программисты хотят писать более короткий и более понятный для всех код. Эта тенденция очень заметна с приходом Python. Посмотрите Swift по сравнению с Objective-C, D по сравнению с C++, и остальные языки, появившееся в последнее время. Все они стремятся визуально облегчить конструкции языка, сохранив их смысл и предназначение. Конечно же это получается не везде.

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

3. До сих пор программисты наблюдают низкую переносимость ранее написанного кода. С библиотеками/фреймворками всё хорошо, но вот модули и классы потерялись в тоннах кода. Никакой ООП, АОП и функциональный подход не улучшают переиспользование такого кода. Эта задача решаема комплексно: легкость языка, функции среды разработки и окружение для конкретного разработчика. Словно портфель знаний Ваш код однажды станет переносим как папка с документами. Вы сможете легко их объединять, разделять, копировать, формировать из их сочетания новые пакеты. Словом всё то, что не было доделано до конца с классами java и много где ещё.

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

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

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

Язык программирования ΣL


В начале несколько фактов о том, почему было выбрано такое название. Первоначально язык назывался WL (от white light), но кто-то известный занял это сокращённое название своим грандиозным проектом Wolfram Language. Поэтому я развернул W на 90 градусов, и это мне понравилось. После же, спросив Интернет, пришло осознание, что повернул я букву не в ту сторону. Несмотря на то, что язык с названием «Сигма» существовал с 1965 до самых 90-ых годов, переименовывать проект ещё раз не было желания.

«Сигма» — язык программирования общего назначения.

Основой языка «Сигма» являются наборы, которые объединяют в себе большинство существующих парадигм программирования. Наборы значительно более мощный и гибкий инструмент, чем объекты. Наборы были с программистами всегда и в каждом языке программирования до этого. Массив это набор однотипных элементов. Функция это набор инструкций. Структуры/записи это набор полей данных. Класс/объект это набор из полей данных и методов-функций. Пространства имён в модулях это тоже наборы, которые содержат в себе всю эту начинку: функции, классы и т.д. Наборы подобно мат. множествам могут объединяться и обладают рядом свойств.

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

Базовый набор носит классическое название source, от него наследуются все остальные наборы.

Набор — это совокупность элементов. Будет вернее сказать, что набор — это интерфейс для совокупности элементов. Элементами являются функции, переменные, другие наборы и т.д.

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

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

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

_ - это множество целых беззнаковых чисел,
__ - множество целых знаковых чисел,
__._ - знаковое, с фиксированной точкой
__,_ - знаковое, с плавающий точкой
_._ - беззнаковое, с фиксированной точкой
_,_ - беззнаковое с плавающей точкой

Есть возможность указывать диапазон значения, например, __[-10:10] — это целое число, которое принимает значение от -10 до 10 включительно.

В языке присутствует необычное ключевое слово «me». Оно является заменой this, но об этом расскажу позже.

Пример кода на языке «Сигма»:

@binary
//это базовый модуль типов для двоичных машин

binary {
  signed {
 //…
  }
  unsigned {
  //описание бита - минимальной единицы данных для двоичной машины
  bit: source { //source - это базовый тип, основа всех наборов
    _[0:1] value;
    state True is { //название состояния регистро-независимо: true, TRUE, True, tRUE, ...
      me.value == 1;
    }
    state False is {
      me.value == 0;
    }
    // операторы, методы и их внутренности опущены для наглядности
    operator := {}
    alloc(){}
    init(){}
    dealloc() {}
  } //bit
} //unsigned

  alias bit signed.bit;
  alias ubit unsigned.bit;
}

Обратите внимание на описание значения бита и его состояния. А так же запомните этот код, мы к нему ещё вернёмся.

Причина такого решения многим не понятна, зато программист сразу привыкает к такому решению и просто использует его. Кажется, что здесь нет проблемы, но на самом деле их было очень много. Чтобы сделать описание состояния настолько понятным и прозрачным ушли месяцы. Можно долго рассказывать о реализациях типов в каждом из языков и о работе условного оператора if, но это утомительно.

Приведённое выше описание явно вводит состояние, которое становится прозрачно описано для программиста, а не скрытно прибито к типу ключевым словом. Грубо говоря, этот код документирует сам себя. В любой момент Вы можете увидеть, что означает то или иное состояние, вследствие чего у Вас отпадает надобность запоминать выдержки документации. Помните такие значения как Infinity, NaN? Теперь их можно явно прописать такой конструкцией.

Состояние набора — это соответствие определённых элементов набора нужным значениям.

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

state False is {
    me.value == 0;
}

Слева у нас подставляется значение в процессе выполнения (me.value), справа заданное описанием статическое множество (0). Таких выражений может быть несколько (разумеется противоречить нельзя, писать второй раз me.value не допустимо).

Делается разность двух множеств. Если они равны, то множество вычитается само из себя (результат пустое множество). У Вас должен возникнуть вопрос, как с этим работает условный оператор (и другие операторы тоже). Оператор if проверяет пустое множество или нет. В случае положительного ответа выполняется последовательность инструкций.

И не бойтесь, при компиляции под целевую платформу всё оптимизируется. Часть кода превратиться в обычные сравнения на целевой платформе.

Главный минус такого решения: нельзя в состоянии прописать условие отличные от тождественных. Например: нельзя писать «me.value >= 0;».

В коде это выглядит достаточно привычно. Ниже показано 4 варианта записи сравнения и 4 варианта присваивания состояния. Все валидны для текущей версии языка ΣL.

use binary;
alias bit binary.unsigned.bit;

bit b := 1;
if (b == 1) { b := 0; }
if (b == True) { b := False; }
if (b is True) { b := binary.unsigned.bit.state.False; } 
if (b.state is True) { b.value := 0; }

В наборе binary.unsigned.bit должны быть реализованы операторы сравнения и присвоения (последний как для числа, так и для состояния). Правила указания их приоритета отдельный разговор.

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

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

Все остальные типы языка для двоичных машин выводятся на основе модуля binary. Например, описание типов под архитектуру х86 начинается так:

@x86

use binary;

x86: binary {
    signed {
        byte {

Аналогично сделано и для десятичной и для троичной архитектуры.

Так же обратите внимание на ключевое слово «use», после его использования привычные «import», «using» и «include» выглядят как «begin» и «end» после сишных скобок. Автор обленился печатать, и его прёт. На самом деле автор видел много людей-программистов, которым это решение очень понравилось.

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

Ключевое слово me заменит self/this и т.п.


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

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

Любую замену для ускорения ввода текста можно довести до абсурда, и в случае языка программирования получить что-то похожее на BrainFuck. По этой причине использование различных иероглифов, символов решётки и тому подобного является абсурдом для замены ключевого слова this. Важное примечание: для кого-то сама такая замена может казаться абсурдом, просто эта статья не для них. Более того ввод подобных символов на наших клавиатурах не всегда предусмотрен и удобен.

Выражения языка программирования должны быть максимально понятны, чётки, коротки (а затем лаконичны и элегантны для восприятия). По возможности следует использовать человекочитаемые фразы. Это помогает запомнить конструкции и быстрее адаптироваться в новом языке. Никлаус Вирт показал успешные примеры таких строгих, чётких и человекочитаемых языков программирования. Но в них были серьёзные недостатки в скорости написания и чтения кода. Простым примером служит конструкция begin end в языке Pascal. Думаю все согласятся, что после фигурных скобок {} языка C, писать begin end просто изнурительно. Новые языки программирования помимо технологий, прошлых наработок и современных падигм должны учитывать вышесказанное.

Каким человекочитаемым и коротким ключевым словом можно заменить современное this, чтобы не исказить его смысл? Как автора нового языка меня этот вопрос тревожил. Очень слабо, но тревожил. Поэтому в комментариях напишите свой вариант, может именно вы предложите «my», от которого я отказался.

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

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

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

В замере на скорость непрерывной печати (в течении первых 6 секунд от начала эксперимента) наша команда продемонстрировала следующие усреднённые результаты:

3 this.
3 self.
5 me.

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

Результаты показали, что в среднем self и this затрачивают одинаковое время в процессе работы программиста. Таким образом, self и this вряд ли могут быть конкурентом друг другу. Если создаваемый набор (класс/объект) редко вызывает собственные методы в реализации и редко обращается к своим полям данных, то использование me не даёт никаких преимуществ. При написании кода в иных наборах (преимущественно с частым обращением к собственным полям данных) на каждые напечатанные 10 self или this приходилось до 16 me, напечатанных за то же самое время. Некоторые коллеги отметили, что при использовании me они больше фокусировались на имени метода или поля, то есть выражение:

me.valueResult += me.valueOne * me.valueTwo;

в момент печати читалось словно me нет совсем:

valueResult += valueOne * valueTwo;

По их словам, такого не происходило с self и this, но я им не верю. На мой взгляд, self и this конечно же съедают больше места в строке кода, чем и обращают на себя внимание при чтении. Кстати, для кого-то это может быть плюсом. Забавный минус — падает скорость чтения кода: «сэлф» и «зис» читается миллисекунды дольше «ми». Вся забава в том, что это правда влияет на быстроту понимания кода человеком.

Важно, что такая маленькая замена, не вызвала проблем. Все испытуемые очень легко переключились на использование ключевого слова me, никто не ощутил дискомфорта. Напротив, всем понравилась такая замена.

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

В любом случае Вы вряд ли испортите свой проект: сочетание Ctrl+H плюс заменить все « me.» (с пробелом в начале) на « this.» вернёт всё как было.

Спасибо, что дочитали.
Пусть терпение и сила прибудет с Вами!
-3
5.4k 12
Comments 87
Top of the day