Эта статья — логическое продолжение статьи “Изменения в стандарте, или Как европейские колхозники превращают Forth в труп”.
В новой статье мысль всё та же: сейчас Форт выглядит как архитектурный бардак. Проблема — в стандарте языка, точнее, в комитете по стандартизации, в котором заседают те, кто так и не понял, по какому пути должен был пойти язык программирования. В результате Форт сильно потерял свои позиции в мировом сообществе. В противовес действиям комитета скажу, что правильная архитектура языка не только приучает фортеров правильно проектировать программы, но и привлекает новых программистов в ряды Форта. Разберем тему на примере слов числового вывода.
Исторический Forth смешивает несколько вещей. У числовых слов внутрь одновременно зашиты:
1. размер числа — single или double;
2. знаковость — signed или unsigned;
3. основание представления — BASE;
4. ширина поля — для .R;
5. собственно алгоритм преобразования числа в последовательность цифр.
Но архитектурно это неправильно: большинство тех различий не должны порождать разные примитивы.
Недостатки нынешнего стандарта обусловлены его историей. Форт создавался как интерактивная система. Человек работает в консоли и пишет:
HEX ABCD FFFF DECIMAL 1000
То есть смена основания — это именно свойство
сеанса работы, а не отдельной функции. Для REPL это великолепно. Для библиотек — уже не очень.
Разберем проблему на примере слова BASE. Сейчас
это неявное глобальное состояние. Само по себе это удобно. Проблемы начинаются, когда BASE становится частью контекста выполнения.
Слова перестают быть чистыми: поведение слова зависит не только от стека, но и от внешнего глобального состояния.
В функциональном мире сказали бы, что BASE — это скрытый аргумент функции.
Проблема – в неявных входных данных. Современный стиль проектирования предпочитает простую композицию слов, без скрытых зависимостей от глобального состояния:
f(x, base)
вместо
global base
f(x)
Потому что первый вариант локален и очевиден.
Исторически в Forth произошло смешение двух совершенно разных задач:
- как интерпретировать текст (123, ABCD и т.п.);
- как представить число пользователю.
BASE решает обе сразу. Это было очень удобно в эпоху телетайпов и интерактивной работы, но архитектурно связывает два независимых механизма.
Чтобы полностью переработать нынешний стандарт, нужно разделить две вещи:
1. Основание ввода REPL — глобальное состояние интерпретатора.
2. Основание форматирования — явный параметр.
Правильная архитектура для нового стандарта должна выглядеть так:
INPUT-BASE — влияет только на интерпретатор текста;
FORMAT — явное преобразование числа в строку;
TYPE — вывод строки;
А слово “точка” — просто сокращение для последовательности слов: DECIMAL FORMAT TYPE
При этом можно было бы написать
Код:
123 HEX FORMAT TYPE
без единого изменения глобального состояния. Это сохраняет лаконичность Форта при использовании слова “точка”, и при этом избавляет язык от одной из самых неприятных скрытых зависимостей.
Если углубиться в проблему наличия в стандарте нескольких слов вывода чисел, то можно затронуть интересную философскую проблему:
количество слов в словаре часто является симптомом не богатства языка, а неудачно выбранных абстракций.
Применительно к Форту:
чем неудачнее выбрана модель языка, тем больше в нем слов как исключений.
Сейчас слова U. D. U.R D.R выглядят как исключения из общего правила. Исторически это объяснимо, но концептуально это всё разные входы в одну задачу. Почему бы не иметь одно слово? Например:
Код:
123 PRINT
И всё. А если нужны формат и форматирование, тогда:
Код:
123 HEX 8 WIDTH PRINT
В таком случае стек описывает
намерение, а не выбирает специальное слово.
Применительно к Форту правильнее было бы записать так:
Код:
123 HEX FORMAT TYPE
Архитектурно это чище. При этом на историческое слово “точка” никто не покушается, потому что оно следует из общего правила:
Код:
: . DECIMAL FORMAT TYPE ;
В Forth много исторических “сдвоенных” слов. Часть из них возникает из-за того, что язык не имеет единой модели объекта или типа данных. Если бы была более строгая концепция “
значение --> представление”, то половина этих исключений исчезла бы.
Давайте зафиксируем правильное понимание:
хороший Forth — это не минимальное количество слов. Это минимальное количество правил, из которых получаются слова.
В этом смысле FORMAT как отдельный слой выглядит гораздо более фортово, чем набор U. D. и т.п. Мы не добавляем новое слово под каждый частный случай, а строим маленький набор базовых операций, комбинация которых покрывает все случаи.
Исторический Forth содержит U. D. U.R D.R . Каждый новый случай требует нового слова. Проблема не в количестве слов. Проблема в том, что каждое слово содержит несколько решений одновременно.
Сейчас слова в Форте потеряли свою чистоту, но если модель такая:
Код:
NUMBER FORMAT WIDTH ALIGN TYPE
то пользователь не изучает отдельное понятие U.R. Он изучает несколько общих понятий, которые комбинируются.
Хороший Forth должен выглядеть как небольшой словарь, но с большим пространством возможностей. При этом словарь может быть и большим, но не за счет новых исключительных случаев.Плохой вариант:
U. D. U.R D.R Потому что каждое слово отвечает на вопрос “какой именно вариант печати?”
Хороший вариант:
FORMAT WIDTH ALIGN TYPE Потому что слова отвечают на вопрос “какие свойства представления числа?”
Конечно, адепт старой архитектуры скажет, что сейчас в Форте всё и так минималистично, но давайте обратим внимание на тот факт, что язык при этом не стал лучше:
ты просто спрятал отсутствие модели за именами. Мы сейчас говорим не о не композиции на уровне реализации, а о композиции на уровне семантической модели языка.
Сам факт того, что слово построено из примитивов, еще ничего не говорит о качестве абстракции. Здесь главный вопрос в том, является ли это слово новым кирпичом здания или просто заплаткой на трещине в архитектуре.
Далее мы плавно переходим к вопросу, какие слова должны существовать в языке как первичные, а какие должны быть выражены через них. Придерживаемся следующих принципов:
1. Словарь остается расширяемым, но
стандарт должен описывать ядро, а не все возможные библиотеки.
2. Минимизировать скрытое состояние. В данном случае таким состоянием обладает BASE.
3. Слова должны выражать понятия, а не случаи.
Если новое требование запрашивает добавления нового слова, возможно, не хватает абстракции.
4. Новый уровень абстракции должен не объяснять существующее, а устранять существующие исключения.
Начнем с простого вопроса: что делает слово “точка”? Кажется, что это одно действие. Но на самом деле внутри происходит несколько разных вещей:
1. Число берется со стека.
2. Определяется его интерпретация: signed? unsigned? single? double?
3. Выбирается основание: DECIMAL? HEX?
4. Число превращается в последовательность символов.
5. Символы выводятся.
То есть это не просто вывод числа. Это маленький комбайн.
На какой стадии начинается проблема? Появляется требование:
Код:
123 .
Хорошо. Но потом появляется требование:
Код:
123 U.
Почему? Потому что оказывается, что слово “точка” не вывело число, а
приняло решение о типе числа.
Потом — 123 D. как еще одно исключение.
Потом — 123 U.R — еще одно.
Потом — 123 D.R — снова еще одно исключение.
А теперь представим, что завтра нужен:
- вывод в HEX;
- вывод с нулями;
- вывод с разделителями тысяч;
- вывод с фиксированной точкой.
Куда добавлять? В итоге это путь бесконечных исключений.
А теперь — правильная модель. Разделяем понятия. Число — это число. Представление числа — это формат. Вывод — это вывод.
В новой модели:
Код:
: U.R ( u width -- )
>R
10 FORMAT
R> WIDTH RIGHT
TYPE
;
То есть U.R не является фундаментальным механизмом. Это просто удобная комбинация.
И здесь появляется вопрос: “Должно ли слово U.R быть частью языка или это просто первая удобная программа пользователя?”
Если пойти дальше, то вопрос будет таким: “Что должно быть в ядре, а что должно быть в слое удобных расширений?”
Мини-комбайн — полезная штука, но никому в здравом уме не придет в голову идея встраивать комбайн в молоток.
И теперь ответ на заданные вопросы будет таким: “В ядре должен быть не комбайн, а несколько простых механизмов”.
А где должны быть “комбайны”, к которым фортеры давно привыкли? Они должны быть в специальном наборе слов с каноническими рецептами под названием PATTERNS. Вообще, правильнее было бы назвать тот набор слов как IDIOMS. Идиома — это не только лингвистический термин, это еще термин из программирования. Идиома — это устоявшийся способ решить задачу средствами языка. Но так как большинство программистов часто являются просто технарями, то для них лучше использовать термин PATTERNS.
Вот готовые рецепты из PATTERNS:
. U. D. U.R D.RЭто не примитивы языка. Это канонические примеры расширения словаря. А теперь — самое главное: мы не запрещаем короткие слова, мы предлагаем не делать комбайны частью ядра. То есть мы не убираем удобные слова. Мы убираем необходимость знать десятки специальных случаев.
В неправильной модели каждая новая потребность рождает новое слово. А в правильной модели комбинация
VALUE + FORMAT + OPTIONS + OUTPUT покрывает всё.
На самом деле сейчас мы не спорим о размере словаря, не спорим о синтаксисе и не спорим о совместимости. Мы задаем для каждого слова всего один вопрос:
”Добавляет ли это слово новое семантическое понятие, или оно лишь фиксирует одну из комбинаций уже существующих понятий?”Если — второе, тогда это кандидат в PATTERNS, а не в CORE.
Но здесь возникает следующий архитектурный вопрос. Если исторические слова являются лишь комбинациями более фундаментальных операций, то мы должны ясно понимать, что именно эти операции комбинируют.
Нам необходимо выделить сам объект преобразования: числовое значение уже определено, но каким образом оно должно быть представлено в текстовом виде?
Это принципиально отличается от интерпретации самого числа. Например, последовательность битов может интерпретироваться как signed или unsigned значение. Это вопрос семантики значения. Но после того как значение однозначно определено, его можно представить в разных основаниях, с разными алфавитами и в разных формах. Это уже вопрос политики представления.
Именно здесь появляется архитектурное понятие
Notation — не как еще одно специальное слово для вывода числа, а как способ описать правила, по которым числовое значение превращается в текстовое представление.
Это понятие естественнее реализовать не обязательно как самостоятельный объект пользовательского стека, а как политику представления (
representation policy), которую использует механизм преобразования числа под названием REPRESENT. Для обозначения этой политики как раз используется понятие
Notation.
Notation описывает, как именно должно быть представлено значение: в каком основании, в какой форме, каким алфавитом и с какой точностью. Конкретное машинное представление такой политики может быть компактно закодировано в descriptor. Таким образом,
Notation — это архитектурное понятие, а
descriptor — его конкретное представление, удобное для передачи и обработки.
Это позволяет отделить саму политику представления от числового значения и от механизма его вывода. При этом
Notation не обязана быть отдельным объектом языка: важно не то, где физически хранится политика, а то, что она становится явным и ортогональным уровнем семантики.
Примерная ортогональная модель выглядит так:
Код:
NUMERIC VALUE
│
│ interpretation
▼
REPRESENTATION POLICY
│
├── RADIX
├── POINT
├── ALPHABET
└── PRECISION
│
▼
REPRESENT
│
▼
TEXT REPRESENTATION
SIGNED/UNSIGNED здесь намеренно не являются свойствами representation policy. Они определяют интерпретацию числового значения, а не способ его текстового представления.
Таким образом, вместо множества специальных слов мы получаем небольшую ортогональную модель политики представления. Она описывает не конкретную операцию вывода, а независимые свойства того, как значение должно быть записано. На ее основе уже могут быть построены различные алгоритмы представления и различные способы последующего вывода.
Мы не утверждаем пока окончательный API REPRESENT. И в то же время мы не просто собрали удобный descriptor. Мы применили критерий:
“Если изменение параметра меняет математическое значение — это не параметр представления. Если значение уже определено и параметр может изменяться без изменения этого значения — это кандидат в representation policy”.
Полное описание этой модели, включая конкретную структуру representation policy, алгоритмы REPRESENT и их реализацию, выходит за рамки настоящей статьи. Здесь важно зафиксировать сам архитектурный принцип: значение, его представление и его вывод — это разные уровни семантики.
Если новичкам в программировании что-то непонятно из вышесказанного, тогда нужно начать с азов: вплотную познакомиться с термином “ортогональность”. Без этой основы в современном программировании делать нечего. Беда нынешнего Форта в том, что в комитете по стандартизации заседают программисты с низкой квалификацией, которые даже не владеют терминологией уровня архитектуры языка.
Как разработчик, который ранее реализовал свой Форт много лет назад, и теперь взялся за новую реализацию, мне видны все недостатки нынешнего стандарта. То, что было простительно в стандарте 1994 года, в котором часть слов сохранили для совместимости, стало преступлением для стандарта 2012 года, который вобрал в себя все прежние недостатки. В текущем стандарте недостатков так много, что я не могу уверенно сказать, что доведу свой проект до конца, потому что приходится втискиваться в ту дефективность, на которой основан стандарт.
Почитал критику в свой адрес, последовавшую после первой статьи. Например, по поводу слова >IN, которое якобы используют в прикладных программах. Во-первых, давайте определим, что называть прикладными программами. Современные языки и экосистемы позволяют прикладным программистам работать на высоком уровне абстракции, не взаимодействуя напрямую с большинством деталей исполнения программы. Во-вторых, в своей первой статье я вскользь упомянул о реализации входного потока в виде отдельного стека, который работает через Input Source Frame (в разных реализациях называется по-разному). Здесь комитет по стандартизации снова свалил всё в кучу, и слова, которые ближе к внутренней работе интерпретатора, были отданы на откуп пользователям. Не потому ли в мире так мало пользовательских программ на Форте, что стандарт изначально непривлекателен для написания программ на этом языке? И если комитет стандартизирует "кишки наружу" у входного потока, то это проблема комитета, а не языка программирования.
Другое дело, что неправильный инструмент сформировал неправильное мышление у некоторых его пользователей. Да, им нравится такой инструмент, они так и говорят: “А мне нравится”. Но проблема в том, что такие пользователи не являются локомотивами Форта. Более того, они являются губителями Форта, потому что язык программирования стал непривлекательным для мирового сообщества и превратился в инструмент для отщепенцев. И это уже отдельная тема, выходящая за рамки программирования. Перефразируя известное выражение о спорте, можно сказать так: “Они не любят Форт в себе, они любят себя в Форте”. Не было бы Форта, они нашли бы другой неправильный инструмент и превозносили бы его до небес, потому что им нравится неправильность в себе. Ребята, это уже попахивает какой-то сектой.
Ну ладно, о слове
>IN еще можно поспорить, но другой пример — слово
BASE. Снова вернемся к теме архитектуры языка. Давайте определимся,
что тащить на стек. Слово BASE оставляет адрес на стеке. Теперь задайтесь вопросом: нужно ли пользователю видеть этот адрес, например, через слово .S ? Посмотрите на абсурдность следующей комбинации:
Код:
BASE .S @
Вы можете себе представить, чтобы пользователю было важно видеть на стеке адрес текущего основания системы счисления? Нет, конечно. А почему? Потому что адрес на стеке — промежуточное звено, которое никого не интересует, потому что после использования BASE сразу используют либо @, либо !. И если адрес — промежуточное состояние, то ему нечего делать на стеке. В таком случае что слово BASE сейчас делает в наборе слов CORE? Вопрос риторический, потому что оно там как пугало огородное. Это архитектурный недостаток интерфейса BASE. Подобные “кишки наружу” отпугивают любых потенциальных новичков, которые могли бы стать основой Форта. Но вместо этого наш язык программирования выглядит для всего мирового сообщества как убожество. Удивительно, что сектанты хвалятся тем, что вместо одного слова, которое могло бы сразу возвращать на стек текущее основание системы счисления, они используют связку из двух. Вот что значит неправильный стандарт, который вырастил поколение программистов с искаженным мышлением.
Некоторые, конечно, уже привыкли работать через связку BASE @, но это потому что их изначально научили ездить объездной дорогой. По этому поводу в английском языке есть меткая язвительная идиома: “To take the scenic route” (“Ездить по живописным местам”). Вот они и ездят по живописным местам, то есть вкруговую, вместо того чтобы ехать по прямой.
В статье мы всё время обсуждаем архитектуру, и лично для меня эта наука проектирования применительно к Форту проецируется на физический образ заброшенного здания где-то на окраине города. В том здании заправляет группа беспризорников во главе с местечковой примадонной. И если фортеры не хотят выглядеть в современном мире группой маргиналов, нужно перестать кучковаться возле того архитектурного сооружения, которое латано-перелатано по причине изначально неправильного проектирования.
Насчет комитета по стандартизации — у русских есть еще одно устойчивое выражение, которое стало крылатой фразой: “Можно вытащить человека из деревни, но нельзя вытащить деревню из человека”.
Эта статья на английском:
1.
https://forth203x.hashnode.dev/forth-s-architectural-mess-the-case-of-the-numeric-output-words2.
https://dev.to/pureforth/forths-architectural-mess-the-case-of-the-numeric-output-words-3c5iP.S. Ключевая фраза во всей статье —
«форт-слово “точка” приняло решение о типе числа». Комитету по стандартизации во главе с примадонной нужно еще 100 лет, чтобы понять простые истины.