Прогресс в ФЯ
Sep. 22nd, 2009 06:23 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Когда делаю для себя на Окамле что-то интерактивное, то обычно делаю веб-интерфейс. Нашел в интернетах простенький веб-сервер в 200 строк (из них половина - перечисление кодов ошибок HTTP) - thumper, портировал под винду (реализовав нехватавшую функцию из модуля Unix), дописал разбор параметров и POST запросов. Устроено там все было очень просто и по-функциональному: для нужных путей регистрируются обработчики, являющиеся вполне себе чистыми функциями - параметры запроса на входе, заголовки и тело ответа на выходе. Этого вполне хватало, но вот пришел день, когда понадобилось обрабатывать много данных и выводить прогресс выполнения. И тут всплыл закон дырявых абстракций.
Как говорят классики, чистое функциональное программирование - это программирование посредством математических функций. Такие функции зависят только от своих параметров и описанных выше значений (эту часть обычно забывают упомянуть, кстати) и возвращают одно значение, больше ничего не делая, т.к. делать-то они ничего и не могут - функция есть просто отображение, математическая конструкция. В математике просто нет такого понятия, как время вычисления функции, там если она описана, то считай, что для всех возможных входных значений результат уже известен. В этом чистая математика разительно расходится с компьютерной действительностью, где присутствует еще одно измерение - время, отчего и возникает столько проблем с временем выполнения программ на чистых языках. Но это я отвлекся.
В данном случае я поступил следующим образом. Раньше обработчик запроса возвращал тело ответа в виде строки. Теперь же он возвращал значение типа
Т.е. это либо строка, как раньше, (чтобы старые обработчики сильно не менять), либо ленивая последовательность строк. Ее сервер выводит, делая flush после каждой порции. Долго работающая операция в моем случае меняла состояние программы - загружала из кучи файлов данные, которые использовались в последующих запросах. Получилось, что загрузка данных - это сайд-эффект, а возвращаемое значение - постепенное отображение прогресса загрузки. Выглядело это примерно так:
Т.е. обработчик запроса возвращает веб-серверу последовательность отложенных вычислений. Веб-сервер последовательно по ней проходится, отдавая результаты пользователю, в результате прогрессбар ползет по мере обработки данных. Все работает, но благодаря тому, что само вычисление стало побочным эффектом. Вот мне интересно, а как такая задача решается вправославном чистом ФП?
Как говорят классики, чистое функциональное программирование - это программирование посредством математических функций. Такие функции зависят только от своих параметров и описанных выше значений (эту часть обычно забывают упомянуть, кстати) и возвращают одно значение, больше ничего не делая, т.к. делать-то они ничего и не могут - функция есть просто отображение, математическая конструкция. В математике просто нет такого понятия, как время вычисления функции, там если она описана, то считай, что для всех возможных входных значений результат уже известен. В этом чистая математика разительно расходится с компьютерной действительностью, где присутствует еще одно измерение - время, отчего и возникает столько проблем с временем выполнения программ на чистых языках. Но это я отвлекся.
В данном случае я поступил следующим образом. Раньше обработчик запроса возвращал тело ответа в виде строки. Теперь же он возвращал значение типа
type content_t = ContString of string | ContEnum of string Enum.t;;
Т.е. это либо строка, как раньше, (чтобы старые обработчики сильно не менять), либо ленивая последовательность строк. Ее сервер выводит, делая flush после каждой порции. Долго работающая операция в моем случае меняла состояние программы - загружала из кучи файлов данные, которые использовались в последующих запросах. Получилось, что загрузка данных - это сайд-эффект, а возвращаемое значение - постепенное отображение прогресса загрузки. Выглядело это примерно так:
let files_work_enum = files |> List.enum |> Enum.mapi (fun i fname -> res_list := (parse_file !g_dics fname) :: !res_list; (* time consuming operation *) let percent = (i+1) * 100 / nfiles in Printf.sprintf "<script>setPercent(%d);</script>" percent) in ...
Т.е. обработчик запроса возвращает веб-серверу последовательность отложенных вычислений. Веб-сервер последовательно по ней проходится, отдавая результаты пользователю, в результате прогрессбар ползет по мере обработки данных. Все работает, но благодаря тому, что само вычисление стало побочным эффектом. Вот мне интересно, а как такая задача решается в