Кон Монад ломает лень в Хаскелле

Я пытался Монаду Конта и обнаружил следующую проблему.

  • Сначала создайте бесконечный список и поднимите все элементы в монаду Cont.
  • Используйте операцию последовательности, чтобы получить монаду Cont в бесконечном списке.
  • Когда мы пытаемся запустить монаду, например, с головой, она попадает в бесконечный цикл пытаясь расширить продолжение, и голова никогда не называется.

Код выглядит следующим образом:

let inff = map (return :: a -> Cont r a) [0..]
let seqf = sequence inff
runCont seqf head

Итак, это ограничение реализации монады Cont в Haskell? Если да, то как это улучшить?

Ответ 1

Причина в том, что даже если значение элемента head sequence someList зависит только от первого элемента someList, эффект sequence someList обычно зависит от всех эффектов someList (и это для большинства монадов). Поэтому, если мы хотим оценить элемент head, нам все равно нужно оценить все эффекты.

Например, если у нас есть список значений Maybe, результат sequence someList равен Just, только если все элементы someList равны Just. Поэтому, если мы попробуем sequence бесконечный список, нам нужно будет изучить его бесконечное количество элементов, если они все Just.

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

test :: (Num a, Enum a) => a
test = flip runCont head $
    callCC $ \esc -> do
        sequence (map return [0..100] ++ [esc [-1]])

или непосредственно используя Cont вместо callCC:

test' :: (Num a, Enum a) => a
test' = flip runCont head $
            sequence (map return [0..100] ++ [cont (const (-1))])

Результат test равен -1. После обработки первых 100 элементов последний элемент может решить избежать всего этого и вернуть -1. Поэтому, чтобы узнать, что является элементом head sequence someList в Cont, нам снова нужно вычислить их все.

Ответ 2

Это не ошибка с монадой Cont так же, как sequence. Вы можете получить похожие результаты для Either, например:

import Control.Monad.Instances ()

xs :: [Either a Int]
xs = map Right [0..]  -- Note: return = Right, for Either

ys :: Either a [Int]
ys = sequence xs

Вы не можете получить любые элементы ys, пока не вычислит весь список, что никогда не произойдет.

Также обратите внимание: sequence (map f xs) = mapM f xs, поэтому мы можем упростить этот пример:

>>> import Control.Monad.Instances
>>> mapM Right [0..]
<Hangs forever>

Есть несколько монад, где mapM будет работать с бесконечным списком значений, в частности, с ленивой монадией StateT и Identity, но они являются исключением из правила.

Как правило, mapM/sequence/replicateM (без конечных подчеркиваний) являются анти-шаблонами, и правильным решением является использование pipes, что позволяет создавать эффективные потоки, которые не пытаются вычислить все результаты впереди. В начале учебника pipes описывается, как решить это более подробно, но общее правило заключается в том, что каждый раз, когда вы пишете что-то вроде

example1 = mapM f xs

example2 = sequence xs

Вы можете преобразовать его в ленивый Producer, просто преобразуя его в:

example1' = each xs >-> Pipes.Prelude.mapM f

example2' = each xs >-> Pipes.Prelude.sequence

Используя приведенный выше пример с Either, вы должны написать:

>>> import Pipes
>>> let xs = each [0..] >-> mapM Right :: Producer Int (Either a) ()

Затем вы можете лениво обрабатывать поток без генерации всех элементов:

>>> Pipes.Prelude.any (> 10) xs
Right True