Первое впечатление
Форт и сейчас известен, главным образом, среди разработки встроенных систем, как что-то вроде необычайного высокоуровневого ассемблера, например, для микроконтроллеров - AmForth и Mecrisp. Однако, когда-то давным давно был известен в другой ипостаси - как язык программирования научных приложений.

Форт был выбран в качестве средства, с помощью которого объясняются детали программной реализации систем, основанных на знаниях, по следующим причинам: во-первых, транслятор с этого языка имеется практически на всех типах микрокомпьютеров, во-вторых, он достаточно дешевый, и, наконец, имеет много общего с языками искусственного интеллекта, в частности с Лиспом.
Таунсенд К., Фохт Д. ПРОЕКТИРОВАНИЕ И ПРОГРАММНАЯ РЕАЛИЗАЦИЯ ЭКСПЕРТНЫХ СИСТЕМ НА ПЕРСОНАЛЬНЫХ ЭВМ. М.: Финансы и статистика, 1990.
Я прочёл это и был впечатлён. Вот три хорошо знакомые мне книги:

Языки программирования в книжках, соответственно - Бейсик, Фортран и Форт! В книге Т. Тоффоли:
CAM-6 является машиной клеточных автоматов, предназначенной для того, чтобы служить лабораторией экспериментатора, средством сообщения результатов и средой для интерактивной демонстрации в режиме реального времени.
Физически CAM-6 состоит из модуля, который вставляется в один разъем IBM-PC (XT, AT или совместимых с ними моделей), и управляющего программного обеспечения, работающего в среде PC-DOS2. В то время как этот легко доступный головной компьютер обеспечивает размещение, экранирование, электропитание, дисковую память, монитор и стандартную операционную среду, вся действительная работа по моделированию клеточных автоматов с очень высокой скоростью совершается самим модулем с быстродействием, сравнимым (для этого частного приложения) с быстродействием CRAY-1. Управляющее программное обеспечение для CAM-6 написано на FORTH и работает на IBM-PC с памятью 256 К. Это программное обеспечение дополнено рядом готовых приложений и демонстрационных примеров и включает полный аннотированный список источников.
В этой книге будет использоваться язык CAM Forth. Forth является расширяемым языком программирования, особенно подходящим для интерактивных задач. Этот язык был расширен так, чтобы он содержал множество слов и конструкций, полезных для поддержания диалога с CAM ( в частности, для определения правил клеточного автомата и для конструирования, документирования и выполнения экспериментов).
Очень интересно! Убедили, я тоже хочу попробовать Форт! Ощутить, как это, писать научное приложение на уровне 80-х или ранних 90-х. Говорят, что каждый фортер пишет свой Форт, но вряд ли это кому-то еще интересно, так что, пожалуй, попробую воспользоваться каким-то готовым Фортом и написать, скажем, программу для численного испытания... А, пускай будет это: C. Clay Marston and Gabriel G. BalintKurti. The Fourier grid Hamiltonian method for bound state eigenvalues and eigenfunctions // J. Chem. Phys. 91, 3571 (1989); doi: 10.1063/1.456888
Как будто на дворе 1989 год, компилятор Фортрана где-то на большой машине в институте, а у меня просто нечто под руками, никаких там Matlab в помине нет. Компьютер - чуть больше по своим возможностям, чем калькулятор.
Знакомство
Кстати, а вот и он - HP 35s. Программы в нём - шитый код (ниже рисунок из Руководства к калькулятору). Перейти с калькулятора на Форт очень просто - Форт тоже использует тот самый шитый код.

В самом деле, каждое слово (термин Форта, аналог процедур или функций) состоит из других слов и так до самого нижнего уровня, где слова-примитивы это просто машинный код.
see normcdf : NORMCDF flit 1.41421 F/ ERF F1.0 F+ flit .500000 F* ; ok see erf : ERF FDUP FSIGN FSWAP FDUP F* FDUP flit .147000 F* FDUP flit 1.27324 F+ FSWAP F1.0 F+ F/ F* FNEGATE FEXP FNEGATE F1.0 F+ FSQRT F* ; ok see fsign : FSIGN F0< DUP INVERT - S>F ; ok see dup DUP IS CODE ( $4012D8 53 ) push ebx
В Форте всё слова. Константа - слово, что оставляет на стеке число. Переменная оставляет на стеке адрес, где лежит её значение. Есть много разных книг и описаний Форта, не буду их повторять, однако, считаю, что одно из лучших описаний того, что и как происходит под капотом дано здесь - http://rigidus.ru/
По моему впечатлению, Форт язык прагматичный, а не парадигматичный. Это постфиксный язык? С одной стороны, да, с другой - не совсем. Вот скажем, если последовательно придерживаться постфиксной записи, то определения должны были бы быть наподобие следующего:(word) cvn { moveto show } def или { moveto show } /S exch def где ключевое слово def (определить) стоит после самого определения. Так делается в Postscript, но не в Форт. В Форте было бы : word moveto show ; - за двоеточием следует определяемый термин, точка с запятой завершает определение. Почему так? А так проще. Двоеточие переводит интерпретатор Форт в состояние компиляции STATE=-1 (true в Форте), и текст определения считывается слева на право, точка с запятой в состояние интерпретации (выполнения) STATE=0.
Многие вещи в Форте сделаны с упором на простоту реализации, а не стандартизации. Чак Мур, создатель Форта, плохо относится к стандартизации. Его принцип - тебе нужно, ты и сделай. Не спекулируй, не оставляй зарубки для удобства будущего расширения функционала. Сейчас удобно и просто делать ТАК, вот ТАК и делай САМ И ПРЯМО СЕЙЧАС. Короче говоря, Форт это не совсем язык, это метод решения конкретных проблем. Вот его кредо:
Keep it simple
Do not speculate
Do it yourself
motherf*cker
Не ждите от Форта серебряной пули, крутого фреймворка, полной библиотеки и продуманного кем-то API. Вот вам мнения, целый зоопарк реализаций и рецептов - да поможет Вам здравый смысл и базовые три принципа.
Стеквилибристика
Положим, что мы захотели реализовать вычисление гамма-функции (точнее, её логарифма) на Форте. Более того, у нас имеется сопроцессор типа Intel 8087 - у него стековая архитектура, очень кстати для Форта! Воспользуемся приближением Ланцоша и запишем:
: LNGAMMA ( x -- ln(Г(x) ) \ Takes x > 0.0 and returns natural logarithm of Г(x). FDUP 3.0E F+ 2.23931796330267E FSWAP F/ FOVER 2.0E F+ -27.0638924937115E FSWAP F/ F+ FOVER 1.0E F+ 41.4174045302371E FSWAP F/ F+ 2.5066284643656E F+ FLN FSWAP FDUP 4.15E F+ FLN FOVER 0.50E F+ F* FOVER 4.15E F+ F- FROT F+ FSWAP FLN F- ;
Да, выглядит не очень читаемо - не все формулы хорошо укладываются в операции со стеком и чем сложнее - тем больше будет слов FDUP, FROT, FOVER... пока не настанет ситуация, когда на стеке 4 и более чисел. Тогда всё, приплыли. Печальная история о том, как такое случается изложена в одном блоге.
Обычный выход из этого положения - локальные переменные. Да, это Форт, их можно реализовать разными способами. Например, так : lngamma { f: x } это способ gforth. Или так : lngamma {: f: x :} способ VFX Forth. Локальные переменные достаточно сложная концепция, включающая в себя область видимости, время жизни и прочее. Вот уже придется нарушить первый принцип Форт и превратить его в Си-подобный язык?
Гиперстатическое глобальное окружение
Строго говоря, глобальные переменные Форта это совсем не тоже самое, что в других языках. Чтобы было понятнее, вот пример рабочего кода:
variable apples ok : +apples apples +! ; ok : apples ." You have " apples @ . ." apples." cr ; ok apples You have 0 apples. ok 5 +apples ok apples You have 5 apples. ok
Здесь переменная apples переопределена словом apples, которое сообщает текущее количество ябл��к. Однако, слово +apples работает как положено, увеличивая счётчик. Внутри слова +apples ссылка на адрес счётчика, а не имя переменной. Так мы можем изменить любое определение не затрагивая работу ранних определений. Например, нам нужна переменная X. Определим:
variable &x : x &x @ ; : (x) &x ! ; : cube (x) x x x * * ; variable &x : x &x @ ; : (x) &x ! ; : square (x) x x * ; 3 square . 9 ok 3 cube . 27 ok
Слова cube и square работают как положено. Повторяющиеся слова &x, x, (x) можно спрятать за определяющим словом, наподобие того, как предложено тут, см. пост FORTH: Самоопределяющиеся слова.
Определение группы переменных в F-PC Forth 3.60
FLOAD FFLOAT.SEQ FLOAD EVAL.SEQ : COMPARE ( c-addr1 u1 c-addr2 u2 -- n ) ROT 2DUP U< IF DROP COMPARE DUP 0= IF DROP 1 THEN EXIT THEN 2DUP U> IF NIP COMPARE DUP 0= IF DROP -1 THEN EXIT THEN DROP COMPARE ; : REFILL ( -- f ) \ CORE version for user input device and string only loading @ IF ( file ) false EXIT THEN 'tib @ sp0 @ = IF ( user input device ) query true EXIT THEN ( EVALUATE ) false ; MACRO: ++ PAD +PLACE ; : (VARIABLE) " VARIABLE &" PAD PLACE 2DUP ++ " : (" ++ 2DUP ++ " ) &" ++ 2DUP ++ " ! ;" ++ " : " ++ 2DUP ++ " &" ++ ( NAME ) ++ " @ ;" ++ PAD COUNT EVAL ; : (FVARIABLE) " FVARIABLE &" PAD PLACE 2DUP ++ " : (" ++ 2DUP ++ " ) &" ++ 2DUP ++ " F! ;" ++ " : " ++ 2DUP ++ " &" ++ ( NAME ) ++ " F@ ;" ++ PAD COUNT EVAL ; : REFILL-AT-EOL? ( S: -- FLAG ) SOURCE NIP >IN @ > DUP 0= IF DROP REFILL THEN ; : VARIABLES( BEGIN BL WORD COUNT 2DUP " )" COMPARE WHILE REFILL-AT-EOL? WHILE (VARIABLE) REPEAT THEN 2DROP ; : FVARIABLES( BEGIN BL WORD COUNT 2DUP " )" COMPARE WHILE REFILL-AT-EOL? WHILE (FVARIABLE) REPEAT THEN 2DROP ;
Так оно будет использоваться так:
\ Объявление переменных VARIABLES( MAXIT ) FVARIABLES( ACCURACY UNLIKELY-VALUE ) \ Присвоение значений -1.11E30 (UNLIKELY-VALUE) 1.0E-9 (ACCURACY) 50 (MAXIT) \ Положить значение переменной на стек MAXIT . 50 ok
Польза от выбранной нотации, по сравнению с обычными переменными Форта, заключается в том, что гораздо чаще с переменной значение считывается, а не записывается. Таким образом, вместо x @ y @ + z ! будет x y + (z), и многочисленные @ и f@ будут факторизованы.
F-PC Forth
Для аутентичного погружения в старые добрые времена IBM PC AT, MS DOS и всё такое, я выбрал F-PC Forth. Скачать fpc36.zip, распаковать, запускать под dosbox. Работает всё из коробки, легко и просто.

Это полноценная IDE, с редактором кода, отладчиком и интерактивной справкой. Удобные IDE не только Borland делал.
больше ретро экранов F-PC Forth 3.60



Написал в этом старом Форте код для поиска корней уравнения методом Риддера. Для сравнения здесь есть код на Julia.
Поиск корня уравнения методом Риддера на F-PC Forth 3.60
DEFER F(X) VARIABLES( MAXIT ) FVARIABLES( XL XM XH XNEW FL FM FH FNEW S RESULT ACCURACY UNLIKELY-VALUE ) -1.11E30 (UNLIKELY-VALUE) 1.0E-9 (ACCURACY) 50 (MAXIT) : FSIGN ( R1 -- R1 ) F0< DUP NOT - IFLOAT ; : F~ ( R1 R2 R3 -- FLAG ) F-ROT F- FABS F> ; : ROOT-NOT-BRACKETED? ( FL FH -- FLAG ) FDUP F0< FOVER F0> AND ( FB ) F0> ( FA ) F0< AND OR NOT ; : RIDDER ( R1 R2 -- R1 ) (XH) (XL) XL F(X) (FL) XH F(X) (FH) FL F0= IF XL EXIT THEN FH F0= IF XH EXIT THEN FL FH ROOT-NOT-BRACKETED? IF ABORT" ROOT MUST BE BRACKETED IN ZRIDDR" THEN UNLIKELY-VALUE (RESULT) FALSE MAXIT 0 DO XL XH F+ 2.0E F/ (XM) XM F(X) (FM) FM FDUP F* FL FH F* F- FSQRT (S) S F0= IF RESULT TRUE LEAVE THEN FL FH F- FSIGN XM XL F- F* FM F* S F/ XM F+ (XNEW) XNEW RESULT ACCURACY F~ IF RESULT TRUE LEAVE THEN XNEW (RESULT) XNEW F(X) (FNEW) FNEW F0= IF RESULT TRUE LEAVE THEN FNEW FSIGN FM F* FM F= NOT IF XM (XL) FM (FL) RESULT (XH) FNEW (FH) ELSE FNEW FSIGN FL F* FL F= NOT IF RESULT (XH) FNEW (FH) THEN FNEW FSIGN FH F* FH F= NOT IF RESULT (XL) FNEW (FL) THEN THEN XL XH ACCURACY F~ IF RESULT TRUE LEAVE THEN LOOP IF RESULT DROP ELSE ." ZRIDDR EXCEED MAXIMUM ITERATIONS" DROP THEN ; : FUNC FDUP FEXP FSWAP -5.0E F* 3.0E F+ F+ ; ' FUNC IS F(X) 1.25E 1.6E RIDDER F.
Кажется, получилось вполне читаемо, особенно если сравнивать с языками тех времен: BASIC, Fortran 77, Pascal.
Структуры, массивы, матрицы
Форт не идет в комплекте с батарейками, но позволяет самому легко задать необходимые конструкции. Не стоит увлекаться чрезмерно общими вещами, ограничимся тем минимумом, что работает. Начнем со структур.
\ Structures : structure: create ( structure name ) here 0 0 , does> @ ; : +field create ( field name ) over , + does> @ + ; : (cell) aligned 1 cells +field ; : (float) faligned 1 floats +field ; : end-structure ( addr size -- ) swap ! ;
Здесь я уже переключился на более современный Форт, стандарта 1994. В сущности, F-PC может быть дополнен до этого стандарта, и код ANS Forth 94 поддерживается современными компиляторами, например, win32forth, Gforth. Следуя духу ретро, я писал код в win32forth.

У него тоже есть IDE и другие удобства, работает под Windows (под wine запускается без проблем). Структуры полезны при определении векторов и матриц, например:
\ Arrays structure: array-structure (cell) .array-data (cell) .array-type (cell) .array-size end-structure : array: ( size -- ) create 0 here .array-data ! here .array-type ! here .array-size ! array-structure allot ; : array-allocate ( vec -- ) >r r@ .array-size @ r@ .array-type @ * allocate throw r> .array-data ! ; : array-free ( vec -- ) >r r@ .array-data @ free throw 0 r> .array-data ! ; : array-element ( i vec -- *vec[i] ) >r r@ .array-type @ * r> .array-data @ + ;
Критические по времени функции нужно выполнить в ассемблере, это даст прирост скорости матричных вычислений 3-5 раз. Работы не много. Ниже код для доступа к элементу массива чисел с плавающей запятой.
code fs-array-element pop eax mov ebx, [ebx] lea ebx, [ebx] [eax*8] next c;
Существует библиотека математических функций Форта - The Forth Scientific Library Project, впрочем, там всё равно нет реализации алгоритма вычисления собственных значений симметричной действительной матрицы. Do it yourself! Берем книгу Голуб, Ч. Ван Лоун. Матричные вычисления и реализуем алгоритм Якоби.

\ Cyclic Jacobi. Algorithm 8.5.3 \ Golub & Van Loan, Matrix Computations fvariables( cos sin EPS ) variables( M EV MAXROT ) 1.0e-10 (EPS) 50 (MAXROT) : eig! (EV) (M) EV matrix-set-identity! MAXROT 0 do M off-diagonal-norm EPS f< if unloop exit then M .matrix-rows @ 0 do M .matrix-cols @ i 1+ ?do i j M sym.schur2 (sin) (cos) cos sin i j M jacobi.rot' cos sin i j M jacobi.rot cos sin i j EV jacobi.rot loop loop loop ." jacobi not converged" ;
Симпатично, практически псевдокод? В этом и есть смысл Форта - создавать лексикон той области, в которой решается проблема. Вспомогательные слова к главному определению eig я опускаю, каждое содержит не больше строк, чем eig.
Финал
Настало время решать поставленную задачу, а именно, согласно статье C. Clay Marston and Gabriel G. BalintKurti. The Fourier grid Hamiltonian method for bound state eigenvalues and eigenfunctions // J. Chem. Phys. 91, 3571 (1989); doi: 10.1063/1.456888 реализовать метод и посчитать, допустим, уровни энергии осциллятора Морзе. Иными словами, превратить рассуждения из фрагмента статьи ниже в практику.

Последовательность действий:
Вычисляем суммы из уравнения (26)
Формируем тёплицеву матрицу H
Создаем дискретную сетку по оси X
Табулируем потенциал V(x) в точках сетки
Добавляем табулированный потенциал к диагонали матрицы H
Находим собственные числа и собственные векторы (это и есть решение)
Суть метода Fourier grid Hamiltonian (FGH) умещается в два определения Форта:
\ Equation 26 fvariables( l d/N ) : sum (d/N) 1.0e (l) 0.0e ( N ) 1 rshift 0 do [ 2.0e fpi f* ] fliteral l d/N f* f* fcos l f**2 f* f+ l 1.0e f+ (l) loop ; variables( diags n ) fvariables( dx 1/n ) : FGH! (diags) (dx) diags .array-size @ (n) n s>f 1/f (1/n) [ -8.0e fpi f**2 f* ] fliteral 1/n fdup fdup f* f* f* dx f**2 1/f f* n 0 do i s>f 1/n f* n sum fover f* i diags fa! loop fdrop ;
Дальше идёт обычный boilerplate code как объявления матриц, векторов, инициализация элементов, затем поиск собственных значений и распечатка результатов. На этом этапе стоит как следует поиграть с параметрами и воспользоваться интерактивностью Форта. Мы же как будто в прошлом, так? Никакого python/numpy, Matlab и Julia - просто усовершенствованный калькулятор.

Кому интересно, смотрите, я выложил код на гитхабе.
Заключение
Форт вполне себе мог c успехом заменять Fortran и что там еще было в то время. Не так то сложно жить с постфиксной записью, стеками и иметь дело с уровнем чуть выше машинных команд. Немаловажно и то, что результатом процесса работы над какой-то задачей в Форте будет или "нет, ну его ко всем чертям, где там это уже сделали, проще списать", или очень глубокое понимание каждой детали и сути происходящего.
Это всё философия, конечно. Однако, я могу себе представить какой-то численный Форт и сейчас, в наше время. Он может оказаться где то глубоко в оборудовании хитрого спектрометра, детектора... Было бы интересно узнать, где.
