Я хотел изучить что-то экзотичное, но в то же время практичное. Критерии практичности на тот момент (несколько лет назад) критерии были такие: среда должна уметь создавать обособленные исполнимые модули, содержащие консольные и графические приложения, работающие под управлением Windows XP и более поздних версий на 32-битной платформе. Если этого минимума нет, язык не рассматривался, так как ничего полезного для себя я на нём всё равно не смог бы написать (на своём тогдашнем ноуте).

Это было нужно для собственного развития. Мне захотелось понять на практике, что такое функциональные языки программирования, так как до этого я изучал только императивные языки, а о функциональных имел представление на уровне формального определения. Этот пробел хотелось заполнить. Factor мне к тому времени уже подвернулся, презентация наGoogle Tech Talksпроизвела хорошее впечатление, а наВикипедиибыло сказано, что язык функциональный, стековый и многопарадигмальный. Это отлично вписалось в идею расширить горизонты моего знания: функциональный - есть, а заодно ещё и стековый, что бы сие ни значило.

Хаскель в те времена ещё только зарождался, Лисп не был практичным по моим критериям, а из прочих языков вообще было не ясно, как выбрать что-то стоящее. Фактор же не только установился и сразу заработал, но и имел богатейшую библиотеку "из коробки", встроенную документацию, интерактивную разработу и многое другое.

Что мне сразу не понравилось после КП и подобных. Текст ошибок, выдаваемых компилятором, иногда не имеет отношения к допущенной ошибке. Эта проблема известна и привычна, я думаю, работающим на C++, но после Delphi и КП есть привычка точно понимать, что пошло не так. В Факторе нет такого строгого синтаксиса, поэтому компилятор сам часто не понимает, что вы от него хотите, от этого взаимный конфуз. В этих случаях приходится переходить на разработку маленькими шагами, чтобы нащупать проблемное место.

Вы спросили об опыте использования. Чтобы мои оценки имели контекст, опишу свой сегодняшний уровень. Я использую Фактор в основном в качестве хобби, для домашних проектов, но иногда пишу и скрипты, которые автоматизируют что-то на работе. За годы знакомства я достиг уровня, когда могу написать средних размеров модуль без ошибок компиляции, но регулярно приходится подглядывать в справку, чтобы уточнить порядок параметров в стандартных функциях или детали их использования. Я работал с GUI (виджеты на OpenGL, таблицы), с файлами, БД (sqlite), многопоточностью (green threads, IPC по TCP), криптографией, делал бинды к сторонним библиотекам (sodium, ...), портировал чужие алгоритмы на Фактор (Ryu). До сих пор не могу сказать, что код на Факторе я напишу быстрее аналогичного кода на Delphi, но я всё ещё надеюсь, что такой переломный момент настанет. В тех случаях, когда в Факторе есть подходящая библиотека (а там их очень много), небольшие проекты удаётся написать за приемлемое время - даже быстрее, чем на Delphi, где надо было бы что-то искать либо изобретать самому.

Пример. Вот скрипт, который можно запустить в корневом каталоге проекта, и он сконвертирует все файлы, у которых нет BOM, из Latin-1 в UTF-8 с BOM.
## Код:
```
! Copyright (C) 2017 Alexander Ilin.
USING:
    io io.directories.search io.files io.encodings.8-bit.latin1
    io.encodings.string io.encodings.utf8 io.pathnames
    kernel namespaces sequences
;

IN: sources-to-utf8

: utf8-signature ( -- str )
    { 0xEF 0xBB 0xBF } utf8 decode ;

: file-to-utf8 ( filename original-encoding -- )
    dupd [ contents ] with-file-reader
    dup "ï»¿" head? [ 2drop ] [
        swap utf8 [ utf8-signature write write ] with-file-writer
    ] if ;

current-directory get { ".pas" ".inc" ".dpr" ".txt" }
find-files-by-extensions [ latin1 file-to-utf8 ] each
```
В своих текстовых редакторах я не нашёл такой фукнции пакетной конвертации. Если немного понимать базовые принципы, то код этот достаточно прост и хорошо читается.

Вот пример, который читается не так легко. Это было написать быстрее, чем в очередной раз изобретать проход по подкаталогам в Delphi:
## Код:
```
! Copyright (C) 2019 Alexander Ilin.
USING:
    formatting
    io.directories io.directories.search io.encodings.utf8 io.files
    io.pathnames kernel math namespaces sequences unicode
;

IN: lower-case-extensions

: except-.git ( seq -- seq' )
    [ "/.git/" swap subseq? ] reject ; inline

: (lower-case-extensions) ( path -- )
    [ dup length swap last path-separator? [ 1 + ] unless ] keep
    recursive-directory-files except-.git [
        over tail dup [ parent-directory ] [ file-stem ] [ file-extension ] tri [
            dup >lower 2dup = [ 5drop ] [
                nip "git mv -f %s %s%s.%s\r\n" printf
            ] if
        ] [ 3drop ] if*
    ] each drop ;

: lower-case-extensions ( path batch-file-name -- )
    utf8 [ (lower-case-extensions) ] with-file-writer ;

: run-in-current-directory ( -- )
    current-directory get "lower-case-extensions.cmd"
    lower-case-extensions ;

MAIN: run-in-current-directory
```

Этот скрипт использует recursive-directory-files для получения списка файлов, начиная с текущего каталога, и создаёт файл "lower-case-extensions.cmd". В результирующем файле содержатся команды git-mv для замены расширения файлов на строчные. Понадобилось, когда увидел, что у нас часть файлов имеет расширение ".Pas" вместо ".pas".

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

Есть задачи, которые очень легко переносятся из естественного языка на Фактор, и такой код очень легко читается. Например, "5 days ago" - это валидный код на Факторе, который вернёт дату-время за 5 суток до текущего системного времени. Аналогично можно написать библиотеки к любым постфиксным нотациям - "5 cm", "15 m", "20 km", etc., и они будут очень естественно встроены в язык.

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

Когда-то очень давно, чуть ли не в школьные или ранние университетские времена, была у меня мысль изобрести некий язык программирования, который бы позволил урезать число потенциальных ошибок до минимума. Что-то вроде библиотеки шаблонов алогоримов над Delphi. Например, нужно нам пройти по массиву от начала до конца, мы берём шаблон прохода по массиву, и внутрь дописываем то, что нам нужно сделать с каждым из элементов. Обычно в Delphi мы применяем некий идиоматичный код типа "for i := 0 to Length(TheArray) - 1 do ...". Проблем с таким подходом много: необходимо уникальное имя для переменной i, саму эту переменную надо где-то объявить, идиома не идентифицируется человеком, который с ней не знаком, в ней можно допустить ошибку, да и вообще, как правило, довольно много текста вокруг нужно прочитать и критериев проверить, чтобы убедиться, что это именно та самая идиома, и что никакие её критерии и правила не нарушены. А если нарушены, то это специально или по ошибке? Не ясно.
Использование шаблона алгоритма решает все эти проблемы, поскольку шаблон однозначно и легко идентифицируется по имени и не даёт нарушить своей структуры, так как структура эта описана в другом месте, а не в месте использования. Кроме того, шаблон может быть улучшен следующим образом: "for i := Low(TheArray) to High(TheArray) do ...". Исправил шаблон - и автоматически новая версия используется везде. Исправить идиому не так просто, особенно в крупном проекте, в том числе потому, что её не всегда легко идентифицировать/найти.

Я с удивлением обнаружил, что в Факторе это всё уже есть и работает. Можно написать любые шаблоны, дать им имя и использовать из библиотек. Например, проход по всем элементам массива (и любой последовательности): "[ ... ] each". Цикл от 0 до X-1: "X <iota> [ ... ] each". При этом анонимной функции внутри [] будут последовательно передаваться значения счётчика от 0 до X-1, и нет ни проблемы с созданием локальной переменной с уникальным именем, ни проблемы с тем, что прикладной код может испортить её значение и сломать цикл, и т.п.

Однако, <iota> - это конструктор неизменяемой виртуальной последовательности с элементами от 0 до X-1.
## Код:
```
! Integer sequences
TUPLE: iota { n integer read-only } ;

ERROR: non-negative-integer-expected n ;

: <iota> ( n -- iota )
    dup 0 < [ non-negative-integer-expected ] when
    iota boa ; inline

M: iota length n>> ; inline
M: iota nth-unsafe drop ; inline

INSTANCE: iota immutable-sequence
```
Означает ли это, что будет создан экземпляр объекта в динамической памяти, у которого итератором будут вызываться виртуальные методы получения длины и очередного элемента (с проверкой диапазона индекса) и всё это будет дико тормозить? Вот, что думает на этот счёт оптимизирующий компилятор:
## Код:
```
[ 5 <iota> [ drop ] each ] disassemble
00000184779bbe60: 89059a5103ff  mov [rip-0xfcae66], eax  ! Предохраняемся от зацикливания.
00000184779bbe66: b805000000    mov eax, 0x5             ! Число итераций.
00000184779bbe6b: 31db          xor ebx, ebx             ! Обнуляем счётчик цикла.
00000184779bbe6d: 4983c610      add r14, 0x10            ! Какой-то пролог.
00000184779bbe71: e909000000    jmp 0x184779bbe7f (( gensym ) + 0x1f) ! Прыжок на проверку условия выхода из цикла.
00000184779bbe76: 48ffc3        inc rbx                  ! Увеличиваем счётчик цикла.
00000184779bbe79: 8905815103ff  mov [rip-0xfcae7f], eax  ! Предохраняемся от зацикливания.
00000184779bbe7f: 4839c3        cmp rbx, rax             ! Проверяем условие выхода из цикла.
00000184779bbe82: 0f8ceeffffff  jl dword 0x184779bbe76 (( gensym ) + 0x16) ! Если не закончили, возвращаемся в цикл.
00000184779bbe88: 4983ee10      sub r14, 0x10            ! Какой-то эпилог, тут я не спец.
00000184779bbe8c: 89056e5103ff  mov [rip-0xfcae92], eax  ! Предохраняемся от зацикливания.
00000184779bbe92: c3            ret                      ! Выход из функции.
```
Код, может быть и не такой чистый, как написанный вручную, но суть в том, что все неиспользуемые абстракции удалены. Во все циклы компилятор автоматически добавляет команду записи в определённую страницу памяти. Если программа зациклилась, её можно прервать, пометив эту страницу как read-only. Я лично добавил поддержку Ctrl+Break в платформенный код под Windows по аналогии с BlackBox.

Программа никогда не "зависает" при долгой работе с вводом-выводом, поскольку все файловые операции сделаны через "overlapped", соответственно, другие кооперативные треды продолжают работать, в том числе отрисовка интерфейса.

Сочетание высокоуровневого кода с достаточно эффективной компиляцией меня очень подкупает. Есть дажепроектпо использованию языка в микроконтроллерах. Кроме того, прилагающаяся библиотека очень богата, и там есть чему поучиться. Иногда поражаюсь, насколько крутые штуки реализованы в модулях на полстраничке-страничке кода. Исходники на Факторе очень компактные, но достаточно читабельные, хотя об этой читабельности приходится специально заботиться и уделять ей время, продумывая разделение функций на именованные куски.

Другой пример полезного класса шаблонов - функции типа with-file-reader: "path encoding [ ... ] with-file-reader". Для сравнения примерный аналог на Delphi:## Код:
```
var
  f: File;
begin
  AssignFile(f, path);
  ResetFile(f);
  try
    ...
  finally
    CloseFile(f);
  end;
end;
```
Другими словами, with-file-reader берёт на себя открытие и закрытие файла, в том числе при возникновении исключений внутри пользовательского кода. Очень удобно и компактно. В Факторе много различных with-* на все случаи жизни. Я позаимствовал этот подход, и уже на Delphi стал писать процедуры, которые принимают на вход анонимную функцию и оборачивают её специфичной обработкой исключений или примитивами синхронизации.## Код:
```
type
  AccessResourceProc = reference to procedure (const Data: TDataRecord);

procedure TClass.AccessResource(Proc: AccessResourceProc; const MaxWaitTime: Cardinal = INFINITE);
begin
  if Mutex.Acquire(MaxWaitTime) then
    try
      Proc(FInternalDataStructure^);
    finally
      Mutex.Release;
    end;
end;

// Usage:
MyClassInstance.AccessResource(
  procedure (const Data: TDataRecord)
  begin
    ... // Handle Data.
  end,
  100); // Wait not more than 100 msec.
```
При таком подходе и Mutex, и FInternalDataStructure остаются скрытыми от посторонних глаз, и доступ к внутренним данным класса TClass возможен только через синхронизанию. Соответственно, забыть вызывать Mutex.Acquire или Mutex.Release у клиентского кода попросту нет никакой возможности, что повышает надёжность кода.

GUI в Factor организовать труднее, чем в Delphi, это недостаток, который в том числе обусловлен кросплатформенной работой поверх OpenGL (стандартные контролы ОС не используются). Конечно, никто не мешает сделать бинды и пользоваться, но в стандартной поставке этого нет (хотя, что-то там из GTK маячило, но я не вчитывался), зато есть свои собственные контролы и механизмы их расположения на окнах. В целом, очень зрелый инструмент для прикладного программирования, на мой взгляд.