Подтвердить что ты не робот

Что делает Iteratees достойной сложности?

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

То, что я действительно хотел бы знать, - это то, почему люди, кажется, считают их такими захватывающими, или при каких обстоятельствах их преимущества оправдывают их сложность. Сравнивая их с ленивыми вводами-выводами, есть очень явная выгода, но для меня это кажется очень сложным, как соломенный человек. Я никогда не чувствовал себя комфортно в отношении ленивого ввода-вывода, и я избегаю этого, за исключением случайных hGetContents или readFile, в основном в очень простых программах.

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

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

Я просто страдаю от не-изобретенного здесь синдрома, или это моя неуверенность обоснована? Или это может быть что-то еще?

4b9b3361

Ответ 1

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

Для меня главными преимуществами итераций являются

  • компонуемости. Мало того, что итерации могут быть составлены, но также могут быть перечислены. Это очень мощно.
  • Безопасное использование ресурсов. Ресурсы (главным образом, память и дескрипторы) не могут избежать их локального масштаба. Сравните с строгим вводом-выводом, где легче создавать утечки пространства, не очищаясь.
  • Эффективное. Итераты могут быть высокоэффективными; конкурентоспособным с или лучше, чем ленивый ввод-вывод и строгий ввод-вывод.

Я обнаружил, что iteratees обеспечивают наибольшие преимущества при работе с отдельными логическими данными, поступающими из нескольких источников. Это когда наиболее удобна компоновка, а управление ресурсами со строгими вводами/выводами наиболее раздражает (например, вложенные alloca или bracket s).

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

enumSound :: MonadIO m => Sound -> Enumerator s m a
enumSound snd = foldr (>=>) enumEof . map enumFile $ sndFiles snd

Это кажется ясным, кратким и изящным для меня, намного больше, чем эквивалентный строгий ввод-вывод. Iteratees также достаточно мощны, чтобы включить любую обработку, которую я хочу сделать, включая запись вывода, поэтому я считаю это очень приятным. Если бы я использовал ленивый ввод-вывод, я мог бы получить что-то столь же изящное, но лишняя забота о том, чтобы ресурсы были потреблены, и GC'd перевесит преимущества IMO.

Мне также нравится, что вам нужно явно хранить данные в итерациях, что позволяет избежать печально известной утечки пространства mean xs = sum xs / length xs.

Конечно, я не использую итерации для всего. В качестве альтернативы мне очень нравится идиома with*, но когда у вас есть несколько ресурсов, которые нужно вложить, которые быстро становятся сложными.

Ответ 2

По существу, это касается ввода IO в функциональном стиле, правильно и эффективно. Это все, действительно.

Корректно и эффективно достаточно просто, используя квази-императивный стиль со строгим IO. Функциональный стиль легко с ленивым IO, но он технически обманывает (используя unsafeInterleaveIO под капотом) и может иметь проблемы с управлением ресурсами и эффективностью.

В очень, очень общих терминах много чистого функционального кода следует шаблону взятия некоторых данных, рекурсивного расширения его на более мелкие части, преобразования частей в некотором роде, а затем рекомбинации его в конечный результат. Структура может быть неявной (в графе вызовов программы) или явной структурой данных, пройденной.

Но это разваливается, когда задействован IO. Предположим, что ваши исходные данные - это дескриптор файла, шаг "рекурсивно расширяться" - это чтение строки из него, и вы не можете сразу прочитать весь файл в памяти. Это заставляет весь процесс read-transform-recombine быть выполненным для каждой строки перед чтением следующего, поэтому вместо чистой структуры "разворачивать, отображать, складывать" они объединяются в явно рекурсивные монадические функции, используя строгий IO.

Iteratees предоставляют альтернативную структуру для решения одной и той же проблемы. Шаги "преобразование и рекомбинация" извлекаются и вместо функций заменяются структурой данных, представляющей текущее состояние вычисления. На шаг "рекурсивного расширения" возлагается ответственность за получение данных и подачу их на (в противном случае пассивную) итерацию.

Какие преимущества дает это предложение? Среди прочего:

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

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

Что касается того, чтобы "стоить сложности"? Ну, вот что - они действительно не такие сложные, просто немного новые и незнакомые. Идея плавала вокруг только, что, пару лет? Дайте ему время для того, чтобы вещи вытряхивались, поскольку люди используют IO на основе итерации в больших проектах (например, с такими вещами, как Snap), а также для появления дополнительных примеров/обучающих программ. Вероятно, в ретроспективе текущие реализации будут казаться очень грубыми по краям.


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

Ответ 3

при каких обстоятельствах их преимущества оправдывают их сложность

Каждый язык имеет строгий (классический) IO, где все ресурсы управляются пользователем. Haskell также предоставляет вездесущий ленивый IO, где все управление ресурсами делегируется системе.

Однако это может создавать проблемы, поскольку объем ресурсов зависит от свойств спроса во время выполнения.

Итераты наносят третий удар:

  • Абстракции высокого уровня, такие как ленивый IO.
  • Явное, лексическое определение ресурсов, например, строгое IO.

Это оправдано, когда у вас сложные задачи обработки ввода-вывода, но очень плотные ограничения на использование ресурсов. Примером может служить веб-сервер.

В самом деле, Snap построено вокруг итерации IO поверх epoll.