Стековое программирование с человеческим лицом (часть вторая)

Как и следовало ожидать, предыдущий пост вызвал противоречивые комментарии. Кого-то устраивает и существующий Форт для решения вопросов, кого-то (как и меня) раздражают его особенности.image

Давайте сразу расставим все точки над i: я не пытаюсь сочинить замену Форту. Форт — семейство среднеуровневых языков программирования, которое продолжает продуктивно решать поставленные задачи и на покой не собирается. Но я размышляю в другой нише: высокоуровневый стековый язык с упором на лёгкость чтения программ для начинающих (насколько это вообще возможно). Большая традиционность и высокоуровневость имеет свои достоинства, но при этом теряются некоторые особенности (в том числе и положительные) Форта.

У нового воображаемого языка появляется своя философия и свои концепции. Об этом я и продолжу писать.

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

repeat любые-слова when условие1 do слова1 when условие2 do слова2 when условиеN do словаN otherwise ветвь-иначе end-repeat Страшно, правда? Но на самом деле это лишь формальное описание, работает всё элементарно. Каждую итерацию выполняются все любые-слова после repeat. Затем происходит вычисление условий when. Если условие1 истинно, то выполняются слова1 и цикл начинает новую итерацию с самого начала. Если условие1 ложно, то происходит переход к следующему условию. Если ни одно из условий не верно, выполняется ветвь-иначе.Этот цикл хорош тем, что из него можно вывести всё, что угодно, то есть это общая основная конструкция.1. Бесконечный цикл (необязательные when и otherwise опущены, они не нужны):

repeat любые-слова end-repeat Предполагается, что где-то внутри цикла будет if с условием завершения и слово exit-repeat.2. Цикл с предусловием:

repeat when условие-истинно? do тело-цикла otherwise exit-repeat end-repeat 3. Цикл с постусловием: repeat тело-цикла when условие-выхода do exit-repeat end-repeat 4. Цикл со счётчиком (возьмём целочисленную переменную counter для примера): repeat counter @ (проверка счётчика) when 100 < do |counter @ ++| counter set (увеличиваем на единицу счётчик, если он меньше 100) тело-цикла otherwise exit-repeat end-repeat 5. Цикл с выходом в середине: repeat какой-то-код when выходим? do exit-repeat otherwise продолжаем какой-то-другой-код end-repeat 6. И даже цикл Дейкстры!Посмотрим, что получилось. Бесконечный цикл интуитивно понятен и лаконичен, никаких лишних слов, поэтому просто оставим его как есть. Цикл с постусловием встречается реже for и while, поэтому в отдельной конструкции смысла нет. Если же такой цикл всё-таки понадобился, его можно легко вывести из общей конструкции, благо он получился ясным и лаконичным.

А вот циклы с предусловием и со счётчиком получились более неуклюжими. Так как они часто нужны, их имеет смысл реализовать в виде отдельных слов:

1. Цикл с предусловием:

while условие do слова end-while 2. Цикл со счётчиком:

for имя-переменной начальное-значение to конечное-значение step шаг do слова end-for 3. И цикл с постусловием (при большом желании):

loop слова until условие-выхода end-loop Обратите внимание на важный момент: циклы while и loop можно реализовать в виде простой текстовой подстановки. Действительно, если while заменить на «repeat when», а end-while на «otherwise exit-repeat end-repeat», то получится общий цикл. Аналогично цикл с постусловием: loop на «repeat», until на », а end-loop на «when not do exit-repeat end-repeat». Сам же цикл repeat при желании можно преобразовать в набор if.То есть нам нужно реализовать в трансляторе только два цикла: repeat и for. Циклы while и loop можно сделать на текстовых подстановках средствами самого языка. Подобный подход принят в Eiffel и Lisp: у нас есть обобщённая конструкция (к примеру, loop в Eiffel и cond в Lisp), которую можно очень гибко использовать. Новые конструкции при возможности реализовываются поверх старой. В Форте принцип противоположен: у нас есть масса частных случаев и инструменты, при помощи которых мы при необходимости сами можем создать нужные конструкции.

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

Условные конструкции Аналогично поступим с условными выражениями. Обобщённая конструкция (привет, Lisp!): cond when условие1 do ветвь1 when условие2 do ветвь2 when условиеN do ветвьN otherwise ветвь-иначе end-cond Работает cond аналогично repeat, но только один раз, а не многократно. Для досрочного выхода предусмотрено слово exit-cond. Cond тоже можно вывести из repeat: нужно просто после каждой when-ветви поставить exit-repeat. Так-то! Хотя при помощи cond можно делать ветвления любой сложности, некоторые часто встречающиеся шаблоны полезно реализовать отдельно.

1. Простейший условный оператор:

условие if ветвь1 else ветвь2 (конечно, необязательная) end-if 2. А вот конструкция case специфична для стекового программирования. Предположим, у нас есть какая-то переменная x и нам нужно в зависимости от значения x выполнить определённую cond-ветвь. Перед каждым when в cond или перед каждым if (в зависимости от способа реализации) нам придётся ставить dup, то есть заботиться о дублировании и/или drop’е лишних элементов:

: testif dup 1 = if .» One» else dup 2 = if .» Two» else dup 3 = if .» Three» then then then drop; «Шумовые» слова здесь совершенно лишние. Действительно, если такие ситуации регулярно встречаются, почему бы не автоматизировать dupы и dropы, повысив читаемость и лаконичность? А если нам нужно сравнить две переменные? А если три? Это же сколько придётся со стеком перед каждым условием мудрить! Специально для подобных ситуаций нужна конструкция case — более «умный» вариант cond. Синтаксис очень похож:

case число-сравниваемых-элементов when условие1 do ветвь1 when условие2 do ветвь2 when условиеN do ветвьN otherwise ветвь-иначе end-cond Основное отличие заключается в том, что каждый раз перед when сравниваемые элементы удваиваются, а перед end-case из стека лишние копии убираются. То есть case = cond + расставленные dup и drop. Число-сравниваемых-элементов указывает, сколько элементов на вершине стека нужно удвоить: x @ y @ case 2 [x y — x y x y] when = do «равны» print-string when < do "y больше" print-string otherwise "x больше" print-string end-case Вводим новые слова и описываем подстановку Ну, строительные блоки у нас уже есть. Остался раствор — определение новых слов. Такие слова вызываются через call, если говорить в терминах языка ассемблера: define имя тело end define-macro имя тело end-macro А вот такие слова работают через текстовую подстановку: имя заменяется на тело. Как нетрудно догадаться, while, loop и if можно реализовать на макросах следующим образом: define-macro while repeat when end-macro

define-macro end-while otherwise exit-repeat end-repeat end-macro

define-macro loop repeat end-macro

define-macro until when end-macro

define-macro end-loop not do exit-repeat end-repeat end-macro

define-macro if cond when do end-macro

define-macro else otherwise end-macro

define-macro end-if end-cond end-macro Введём ещё одно знакомое слово для красоты: define-macro break! do exit-repeat end-macro Теперь можно очень лаконично и понятно писать что-то вроде repeat … when 666 = break! … end-repeat Итого: благодаря гибким конструкциям repeat и cond при помощи элементарной текстовой подстановки можно реализовать целый набор строительных блоков на все случаи жизни. В отличие от Форта, совершенно не приходится думать о реализации слов, мы абстрагируемся от деталей реализации.

К примеру, слово when в cond и repeat носит скорее косметический характер: оно позволяет визуально отделить условие от прочих слов. Но на самом деле какую-то роль играет лишь слово do. Хотите предельной лаконичности?

define-macro ==> when do end-macro и пишите cond x @ y @ = ==> «равны» x@ y @ < ==> «y больше» otherwise «x больше» end-cond print-string совершенно не задумываясь над реализацией транслятора. Это не наша забота, мы на высоком уровне! В следующий раз разговор пойдёт о разборе транслятором исходного кода программы, о стеках, данных и переменных.

© Habrahabr.ru