Прогресс в ФЯ
Sep. 22nd, 2009 06:23 pmКогда делаю для себя на Окамле что-то интерактивное, то обычно делаю веб-интерфейс. Нашел в интернетах простенький веб-сервер в 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 ...
Т.е. обработчик запроса возвращает веб-серверу последовательность отложенных вычислений. Веб-сервер последовательно по ней проходится, отдавая результаты пользователю, в результате прогрессбар ползет по мере обработки данных. Все работает, но благодаря тому, что само вычисление стало побочным эффектом. Вот мне интересно, а как такая задача решается в
no subject
Date: 2009-09-22 01:11 pm (UTC)no subject
Date: 2009-09-23 05:37 am (UTC)no subject
Date: 2009-09-22 05:58 pm (UTC)спасибо.
no subject
Date: 2009-09-22 09:08 pm (UTC)нагуглил. :)
no subject
Date: 2009-09-23 05:32 am (UTC)Не могу ответить в коментах на твой пост 2009-09-22, т. к. у тебя
отключён OpenID, а регистрироваться в ЖЖ не очень хочется.
Возможно, для ленивого формирования ответа серверу достаточно будет
функций модуля Data.ByteString.Lazy? Точно не знаю. В Python это
решается за счёт Iterable(bytes), где Iterable может производить,
например, эффекты чтения при генерации ответов, см. WSGI.
P. S. Нельзя ли как-нибудь более либерально относиться к аутентификации
в блоге? :)
Поменял настройки журнала, теперь, по идее, должен быть доступ не только для участников ЖЖ. Старые настройки были не со зла, а по незнанию, сорри.
Собственно мое решение именно такое, как Вы предлагаете. Окамловский Enum - это аналог Iterable или IEnumerable в других языках. Это работает, потому что разрешены побочные эффекты.
no subject
Date: 2009-09-23 10:37 am (UTC)