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

Что произойдет, если Enumerator попытается использовать вход?

Определение Enumerator:

type Enumerator a m b = Step a m b -> Iteratee a m b

В документации указано, что в то время как Iteratee данные comsume, Enumerator произведите его. Я могу понять, как можно создавать данные с таким типом:

enumStream :: (Monad m) => Stream a -> Enumerator a m b
enumStream stream step =
    case step of
        Continue k -> k stream
        _          -> returnI step  -- Note: 'stream' is discarded

(enumEOF сложнее, чем это... он, по-видимому, проверяет, чтобы Iteratee не выполнял Continue после отправки EOF, вызывая ошибку, если это произойдет.)

А именно, Iteratee создает Step, когда он запускается с runIteratee. Этот Step затем подается в мой счетчик, который снабжает его Stream, чтобы он мог продолжить. Мой перечислитель возвращает полученное продолжение.

Одна вещь выделяется на меня: этот код работает в монаде Iteratee. Это означает, что он может потреблять данные, верно?

-- | Like 'enumStream', but consume and discard a chunk from the input stream
--   simply because we can.
enumStreamWeird :: (Monad m) => Stream a -> Enumerator a m b
enumStreamWeird stream step = do
    _ <- continue return            -- Look, mommy, I'm consuming input!
    case step of
        Continue k -> k stream
        _          -> returnI step

В документации указывается, что когда перечислитель действует как источник и приемник, вместо Enumeratee:

type Enumeratee ao ai m b = Step ai m b -> Iteratee ao m (Step ai m b)

Однако, видимо, мне не пришлось; Я мог бы использовать вход в определении Enumerator, как показано в моей функции enumStreamWeird.

Мои вопросы:

  • Что произойдет, если вы попытаетесь "потреблять" данные в Enumerator, например, enumStreamWeird? Откуда берутся данные?

  • Даже если мы недостаточно сумасшедшие, чтобы потреблять данные в перечислителе, действительно ли он действителен для выполнения действий в основной монаде от имени перечислителя, а не от имени итератора, читающего данные, репродукция?

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

4b9b3361

Ответ 1

Да, перечислитель может использовать данные. Перечислитель в основном принимает итерацию и преобразует ее в одну итерацию после того, как ее накормили некоторые элементы. Если перечислитель запрашивает ввод, то полученный итератор будет запрашивать ввод.

Как перечислитель пересылается в Iteratee

Посмотрим, как перечислитель пересылается в iteratee:

-- | Feed an Enumerator to an Iteratee.
feed :: Monad m
     => Iteratee a m b
     -> Enumerator a m b
     -> Iteratee a m b
feed iteratee enumerator =
    Iteratee $ do
        step <- runIteratee iteratee
        runIteratee $ enumerator step

Примечание: feed - частный случай >>==.

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

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

Пример 1: Подача счетчиков на итерацию

Предположим, что у нас есть итерация, которая запрашивает три строки и печатает их:

iter3 :: Iteratee String IO ()
iter3 = do
    lift $ putStrLn "Gimmie a string!"
    a <- head_
    lift $ putStrLn a
    lift $ putStrLn "Gimmie another string!"
    b <- head_
    lift $ putStrLn b
    lift $ putStrLn "Gimmie one more string!"
    c <- head_
    lift $ putStrLn c
    lift $ putStrLn "Thank you!"

head_ определяется в Data.Enumerator. Список.суб >

и перечислитель, который передает его итерацию одной строкой:

getString :: Enumerator String IO a
getString (Continue k) = do
    line <- lift getLine
    k (Chunks [line])
getString step = Iteratee $ return step

Когда getString задается итерация, которая требует более одного элемента, она будет кормить итерацию первым элементом. Тогда самому getString понадобятся остальные элементы.

  • iter3 требуется три элемента, прежде чем он сможет вернуть ().

  • iter3 `feed` getString требуется два элемента.

  • iter3 `feed` getString `feed` getString нужен один элемент.

  • iter3 `feed` getString `feed` getString `feed` getString больше не требуется элементов.

  • iter3 `feed` getString `feed` getString `feed` getString `feed` getString эквивалентно приведенному выше. Это обрабатывается вторым случаем getString.

Пример 2: Перечислитель, который потребляет входные данные

Рассмотрим перечислитель, который потребляет вход:

consumeEnum :: Enumerator String IO a
consumeEnum step = do
    lift $ putStrLn "I take without giving"
    _ <- head_
    Iteratee $ return step

Что делает iter3 `feed` consumeEnum? На это можно ответить, посмотрев на собственную реализацию consumeEnum. Сначала ему нужен элемент и отбрасывает его. Затем он передает факел на iter3, для которого требуется еще три элемента.

Однако вернемся к комбинатору feed. Он начинается с запуска iter3, затем передает Step значение consumeEnum. Это означает, что "Gimmie a string!" будет напечатано до того, как управление достигнет consumeEnum.

Ответ 2

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