Skip to main content

Блог инженера

Блог о минимализме, инжинерии и программировании.



Черим линию из точек. Часть 3

  | #Форт#программирование

После первой неудачной попытки реализовать компьютерную графику на языке программирования Форт. И второй, удачной, попытки реализовать компьютерную графику на Си я снова попробовал вернуться к Форт. Для этого я вставил в pForth собственные функции для рисования непосредственно в графических файлах в формате bmp. На этот раз всё заработало великолепно. Но теперь я понимаю, почему на Форте больше никто не пишет. Я продолжу, пожалуй, для меня это интеллектуальное развлечение. Но теперь я понимаю, что с точки зрения профессионального программирование некоторые плюсы языка Форт не перевешивают его очевидного минуса. Для понимания, что происходит на стеке нужна немалая практика. Она приобретается, я уверен. Но входной порог высок. И даже насмотренному программисту не получится понять всю логику работы с одного взгляда.

Для примера - ниже два листинга программ. Первый - на Си. Второй - на Форт. Они делают один и тот же рисунок. В обеих случаях собственно графическая библиотека вынесена из текста программы. В Си она подключена, как внешняя библиотека. В Форт она “вшита” в сам язык. Также я пытался сделать код хоршо откоментированным и ясным. То, что в программе на Форт комментариев больше - следствие сложности.

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

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 под эти цели.

About Mikhail Kiselev

Photo of Mikhail Kiselev

Приветствую в моём блоге! 😄 Меня зовут Михаил. Я инженер и программист. Живу в Сибири, работаю на Сахалине, путешествую где придётся. Я предпочитаю пост в блог посту в твиттер. Описание полезной технологии или гаджета предпочитаю описанию заката или посиделок в кафе.