Свалка идей. Возможно фантастических. Возможно реальных.
Цель - скорость, полностью избавиться от парсинга форматной строки в рантайме.
См. http://caml.inria.fr/pub/ml-archives/caml-list/2002/04/1d6d46bf8aa8f5fa8ac3bdac54bf96d1.en.html
При первом проходе - синтаксически заменять все print expr
на let _x = expr in ()
и после компиляции смотреть тип expr через дамп TypedTree или через -annot. При втором проходе используя информацию о типах синтаксически собирать принтер всего выражения из принтеров составных частей (которые определены для примитивных типов и типов из stdlib) и подставлять этот конкретный принтер в исходный код. Возможно задача сводится к автоматическому выводу нужного аргумента (типа) для Show.show<\_\> (deriving) и полуавтоматической реализации модулей Show\_ для существующих типов (из stdlib).
Косяки :
полиморфные значение остаются полиморфными (т.е. не печатаются)
может влиять на вывод типов (?)
Есть подозрение что текущая реализация субоптимальна, из-за чего создаётся много новых коротко-живущих потоков.
Чисто синтаксически преобразовывать
let! sock = connect addr in
let! ping = read sock in
let answer = "pong " ^ ping in
let! () = write sock answer in
()
в
connect_async addr (fun sock ->
read_async sock (fun ping ->
let answer = "pong " & ping in
write_async sock answer (fun () -> ())))
Все \*\_async вызовы обрабатываются через один общий асинхронный менеджер.
Вопросы:
обработка ошибок
только для IO?
исключения
вообще возможно ли это только на уровне синтаксиса?
а не повторение lwt ли это?
Дополнительный callback в структуре custom\_ops для нотификации о смене адреса value при сборке мусора (два случая - minor-\>major и compaction), позволит сишным биндингам явно сохранять указатель на ocaml значения и обновлять его при (редком) перемещении.
см. http://chatlogs.jabber.ru/ocaml@conference.jabber.ru/2010/08/06.html\#18:54:04.836275
Есть применение для такой штуки: есть файл, в котором раскидана окамловская структура данных, по которой можно ходить с помощью определённых функций, менять, дописывать, мусорособирать по-тупому. Персистентность, что ли. Что-то типа ancient, только в файле. Либо, то же самое, что маршаленная огромная структура данных, только без маршаллинга всей структуры туда-сюда. Кеш -- либо дать ОС делать свою работу, либо сделать очень тупой кеш (основанный на хинтах типа "пока эта функция выполняется, лучше это значение / эту страницу подержать в памяти чуток"). Сборку мусора уже кое-как асилил (завести второй файл, куда скидывать живые данные вплотную, заменяя в первом файле смещения значений на те, которые во втором, ставя признак "значение в другом файле", однако есть сложности с изменяемыми значениями. Либо, вариант, вместо сборки мусора поддерживать free list). Не продумал flash-friendly writes, чтобы если оно на флешке, то флешка не угибалась раньше времени. Вообще, надо почитать про "on-disk data structures", в том числе в применении ко всяким ssd. То есть, если в персистентную структуру положить какое-нибудь type tree 'i 'a = [ Node of 'a | Tree of 'i and 'i and list (tree 'i 'a) ] (где в конструкторе Tree будут минимальное значение ключа в поддереве, максимальное, и собственно поддерево), то получим что-то типа имплементации btree поверх файлов. (тип взят от балды.) Если кому-нибудь такое/подобное тоже надо будет -- давайте думать вместе.
Пока принцип можно сделать тупым: файл, в который только дописываются новые значения. Может потом учесть что-то про мусорщика, чтобы дописывать с тегами. Может наоборот, учитывать про free list. Надо думать. По первым прикидкам, можно оформить модуль для сериализации значений (вдруг кому не нравится Marshall, это нормально), и модуль для работы с персистентными значениями:
module Pers
=
struct
type pers 'a;
value get : pers 'a -> 'a;
value modify : pers 'a -> 'a -> unit;
end
Кроме того, вертится в голове кое-что на тему того, что можно из рекурсивной структуры вида type ty 'a = { selfref1 : ty 'a } сделать структуру type ty_pers 'a = { selfref1 : ty_pers 'a }, надо подумать про http://alain.frisch.fr/soft#recursive или подобное. В общем, если структура является функтором (в смысле теории категорий), то её можно легко превратить в персистентную, просто отобразив её функцией, превращающей окамловское значение в смещение этого же значения, сериализованного в файле. Подумать, стоит ли вообще работать с не-функториальными структурами данных. Из них сходу -- абстрактные структуры данных, с которыми работать не можем никак, без их помощи (без сериализатора, например), и функциональные значения, которые точно не нужно писать на диск.
Кроме того, надо как-то учитывать корневые значения, хотя бы для сборки мусора. Например, оформить mutable single-linked list, в хвост которого дописывать новый узел, хранящий новое корневое значение (вероятно даже средствами самой "библиотеки" -- хранить в файле "type roots = { val : Obj.t ; next : mutable option roots }", хотя подумать про типы -- нужно ли / можно ли туда тащить Obj.). Конечно, потребуется знать, где последний узел списка хранится (а это, вероятно, дофига seek+read, но можно оптимизировать), но зато для записи -- весьма дружественная структура, перезаписывает не один фиксированный блок файла, а каждый раз разный.
И надо учитывать какие-то именованные значения для взятия конкретных значений из файла, типа get_named_value : string -> pers 'a; с пользовательским типом 'a. Как input_value например. Либо подумать про сериализацию вообще, как безопасней сделать её, но чтобы в случае "НАДА СКОРАСТЬ" не терять особо. Подумать, как совместить сборку мусора с записью. Например, если нужно записать страницу, но не прямо сейчас, и вскоре нужно записать в файл новое значение, то смотреть, можно ли его поместить на эту страницу, которая всё равно уйдёт на диск.
Подумалось, как можно оптимизировать перезапись блоков для flash-friendly writes. Два варианта: 1. оформить корневые значения в виде mutable single-linked list, и добавление нового корневого значения вызовет запись в двух блоках: в хвосте, где nil, и в непосредственно новом блоке (плюс к тому, нужны какие-нибудь несложные индексы для быстрой навигации по данному списку). 2. постановить, что последний блок файла (4096 байт, например) всегда содержит служебную информацию (например, указатель на первое и последнее корневое значение), и при расширении файла сначала рисовать новый "последний блок", а затем уже писать в файл данные. Либо так: последние 4 байта содержат размер этого блока, перед ними идёт блок, остальное -- значения. И можно оптимизировать запись тем, что выделять сразу много блоков, а не двигать последний блок каждый раз на 4кб.