Стан справ у моїй віртуальній машині SECD: після того, як я таки наважився ввести в її пам’ять heap для масивів, все понеслось із великою швидкістю і зараз це уже більше Scheme, ніж PicoLisp.
Масиви довго мене лякали і я читав статті про опис масивів залежними типами, здригаючись від перспективи виставити в відносно функціональне середовище явний set! над пам’яттю. Але коли я таки написав масиви, код сам все прояснив: vector-set! (присвоєння клітинці значення) написався сам собою як явний drop попереднього значення в клітинці масива і init_with_copy іншим значенням. Таким чином: у мене є масиви, на яких я можу робити мутабельні операції через vector-set!, вони відносно контрольовані і в достатній мірі нагадують IORef - сам масив це тільки незмінний handle, посилання на реальну пам’ять в кучі. Мене також лякала комбінація reference counting та володіння клітинок масивами - але копіювання значень також дуже просто все вирішило: «звичайна», спискова куча не може володіти клітинкою із масива, вона лише може отримати копію значення, отже, клітинка масива завжди має неявний refcount 1 (хоч на сам масив можуть посилатись скільки завгодно клітинок будь-звідки).
Коли в мові є масиви, рядки робляться елементарно: CELL_STR трактує свою пам’ять не як масив клітинок (як CELL_ARRAY), а як набір байт, причому для приємної розминки я вплиляв туди ще й
власне декодування/кодування UTF-8 (конструктивна критика вітається), так що string-length рахує правильну кількість codepoints (да, я знаю різницю між символом і codepoint).
Вчора дописав тип CELL_BYTES, який відповідає схемівському bytevector та підтримку його синтаксису (#u8(77 101 108 108 111 0) - представлення в ASCII рядка "Hello").
I/O довго зводився до двох опкодів, READ та PRINT, які приблизно відповідали нативній реалізації (read) та (write). Недавно це змінилось: з’явився тип клітинки CELL_PORT, за яким можливі два бекенда: рядковий (у пам’яті) та POSIX-файловий (тобто схемівські (open-input-string "data here") та (open-input-file "/path/to/file.scm"). Це одразу абстрагувало проблеми із тим, як SECD працює всередині мого недоядра, де досі немає файлової системи і де дані вкомпільовуються в бінарник). Таким чином, зараз уже працюють open-input-file, open-input-string, read-char, read-string, close-port. Вивід (open-output-file, write-string, etc) досі не неписаний, але в разі потреби напишеться за десять хвилин.
Про підтримку платформ: збирав SECD на Андроїді в TerminalIDE, поки що не працює через закриті дефолтні stdin та stdout. Також вкомпільовував у свою спробу написати POSIX-подібне ядро, COSEC. І основна платформа, звичайно ж, POSIX.
Ще один амбіційний напрямок - додати в машину зелені потоки та неблокуче I/O. Це, звичайно, потребує, щоб усі нативні операції мали свій watchdog, який обмежує час їх виконання, та могли відновлювати своє виконання. Але загалом, враховуючи дуже явний стан машини, збереження стану потоку виглядає дуже просто. І взагалі, цікаво спробувати компілювати Erlang у SECD (для цього, в принципі, все є, за винятком tuples, а з рядками у мене уже все краще, ніж у BEAM).
Можливо, маячня, але подумалось, що можна реалізувати контроль над ефектами у мові як обмеження неймспейсів, до яких має доступ функція. Строго (і по темі SECDScheme) кажучи, створити віртуальний модуль io, куди зібрати всі ті функції, які б у Хаскелі були в IO.
Правда, один абстрактний підводний камінь я уже ніби бачу - модулі це множини функцій у наївному розумінні, так що це типізація у наївній теорії множин (правда, не сказав би, що я багато розумію в теорії типів). Із практичних наслідків - що незрозуміло, чи всі комбінації ефектів матимуть сенс і наскільки гнучкий такий механізм. Покритикуйте, будь-ласка (можливо, хтось уже натрапляв на якусь студентську спробу склепати «монади на модулях»).