После первой неудачной попытки реализовать компьютерную графику на языке программирования Форт. И второй, удачной, попытки реализовать компьютерную графику на Си я снова попробовал вернуться к Форт. Для этого я вставил в pForth собственные функции для рисования непосредственно в графических файлах в формате bmp. На этот раз всё заработало великолепно. Но теперь я понимаю, почему на Форте больше никто не пишет. Я продолжу, пожалуй, для меня это интеллектуальное развлечение. Но теперь я понимаю, что с точки зрения профессионального программирование некоторые плюсы языка Форт не перевешивают его очевидного минуса. Для понимания, что происходит на стеке нужна немалая практика. Она приобретается, я уверен. Но входной порог высок. И даже насмотренному программисту не получится понять всю логику работы с одного взгляда.
Для примера - ниже два листинга программ. Первый - на Си. Второй - на Форт. Они делают один и тот же рисунок. В обеих случаях собственно графическая библиотека вынесена из текста программы. В Си она подключена, как внешняя библиотека. В Форт она “вшита” в сам язык. Также я пытался сделать код хоршо откоментированным и ясным. То, что в программе на Форт комментариев больше - следствие сложности.
В качестве модели для рисования я использовал Godseye. В основном потому, что алгоритм очень прост и мне нравится узор.
Чертим GODSEYE программой на Си
#include "graphlib.h"
#define SIDES 15
#define LINE_SCALE 100.0
/* Creates Godseye */
int main( int argc, char* argv[] )
{
int i; // for cycle counters only
int id1, id2;
/* Create canvas for drawing */
InitGraph ( 300, 220, 800, 600 );
SetOrigin( 150, 110 );
/* Draw N-gon using precalculated vertex coordinates */
for ( i = 0 ; i < (SIDES+1) ; ++i )
{
id1 = (i * LINE_SCALE / SIDES);
id2 = (( SIDES - i ) * LINE_SCALE / SIDES);
MoveTo(id1, 0);
LineTo(0, id2);
LineTo(-id1, 0);
LineTo(0, -id2);
LineTo(id1, 0);
}
CloseGraph();
return 0;
}
Возможно, дело в “насмотренности” программ на Си, но это легко читаемый и понимаемый код на императивном языке программирования. Чёткая структура, названия функций достаточно говорящие, чтобы понимать о чём они, а детали реализации можно подсмотреть в исходном коде с подробными комментариями.
Чертим GODSEYE программой на Форт
: SIDES 15 ;
: NEG -1 * ;
: LINE-SCALE 100 * ;
: DRAW-DIAMOND ( id1 id2 -- )
DUP 0 ( id1 id2 id2 0 )
MOVETO ( id1 id2 )
SWAP ( id2 id1 )
DUP 0 ( id2 id1 id1 0 )
SWAP ( id2 id1 0 id1 )
LINETO ( id2 id1 )
SWAP ( id1 id2 )
DUP ( id1 id2 id2 )
NEG 0 ( id1 id2 -id2 0 )
LINETO ( id1 id2 )
0 ROT NEG ( id2 id2 0 -id1 )
LINETO ( id2 )
0 ( id2 0 )
LINETO ;
: DRAW-EDGES
SIDES 1+ 0 DO
SIDES I - LINE-SCALE SIDES / ( id2 )
I LINE-SCALE SIDES / ( id2 id1 )
DRAW-DIAMOND
LOOP ;
: GODSEYE
300 220 800 600 INITGRAPH
150 110 SETORIGIN
DRAW-EDGES
STROKE
CLOSEGRAPH ;
В скобках после большинства строк - комментарий с ожидаемым состоянием стека. Без них мне было бы непросто разобраться с тем, что проихсодит. Названия слов относительно говорящие, можно было бы добавить комментариев. Когда появляется “начитанность” и можно читать относительно несложный код на Форт без диаграм стека в комментариях и совмещать строки, которые сейчас разбиты на части для облегчения понимания. Особенно это касается слова DRAW-LOZENGE, где много эквилибристики со стеком.
Но я решил использовать особенность pForth - локальные переменные внутри слов. Это сделало код более читаемым, но привязанным к pForth. Ниже второй листинг программы. К сожалению, в классическом ANS Форт локальных переменных не предусмотрено. Думаю, это была одной из многих причин того, что язык вымер как мамонт.
: SIDES 14 ;
: NEG -1 * ;
: LINE-SCALE 100 * ;
: DRAW-DIAMOND { id1 id2 -- }
id1 0 MOVETO
0 id2 LINETO
id1 NEG 0 LINETO
0 id2 NEG LINETO
id1 0 LINETO ;
: DRAW-EDGES
SIDES 1+ 0 DO
SIDES I - LINE-SCALE SIDES / ( id )
I LINE-SCALE SIDES / ( id1 id2 )
DRAW-DIAMOND
LOOP ;
: GODSEYE
300 220 800 600 INITGRAPH
150 110 SETORIGIN
DRAW-EDGES
STROKE
CLOSEGRAPH ;
Локальные переменные “стоят” недорого, но облегчают чтение программы. Сама по себе концепция стека проста, но сложно даётся жонглирование числами на стеке. Поверьте, первый листинг - результат разумной оптимизации перваоначального варианта, который я не публикую. Он был нечитаем. В итоге, Форт заставляет внимательно относиться к коду и оптимизировать его ещё на стадии написания.
Теперь о некоторых субъективных плюсах языка:
- Форт поощряет декомпозицию программ. Считается хорошим стилем делить программы на как можно более короткие функции. Проблема в том, что вызов функций в большинстве языков ухудшает читаемость программы. Мы что-то отдаём и что-то получаем. В Си и Си++ нужно думать об ограничении на возвращение функцией одного значения. Значит, приходится либо созадвать структуры данных вместо переменных, даже там, где они не нужны. Либо передавать ссылки на значения, а потом использовать соответствующих синтаксис для работы с ними. Кроме того, каждый вызов функции что-то “стоит” и снижает скорость и эффективность программы. Это малосущественно, но таких вызовов тысячи. За счёт того, что Форту ничего специально передавать не приходится, всё, что нужно уже на стеке - вызов слова ничего не “стоит” и прибегать к декомпозиции на короткие кусочки легко и приятно.
- Эффективный код. Сама по себе ограниченность возможностей стимулирует к более эффективному коду. Использованию арифметики с фиксированной точкой вместо плавающей точки, упрощение кода. Мне ещё потребуется сравнить скорость выполнения кода между программой на Форт и Си. Пока это чисто умозрительное заключение.
- Простой синтаксис и широкие возможности его расширения. В Форт изначально нет “синтаксического сахара”. Про его вред много пишут, но в современных языках программирования его всё больше. В итоге появляется четыре разных синтаксических конструкции для записи одного кода и это снижает читаемость кода. Ну, не настолько, насколько стековые манипуляции Форт, но всё же. В то же время, возможности языка Форт позволяют создавать собственные синтаксические конструкции под конкрнетную задачу. Я видел Форт-программы, которые были образцом читаемости и ясности. Другое дело, сколько усилий потратили программисты на создание DSL под конкретную задачу. Трудно сказать, стоит ли оно того, но это красиво.
Думаю, есть и ещё преимущества. Я планирую продолжить реализовывать простейшие алгоритмы компьютерной графики в Форт и попробовать сделать DSL под эти цели.