Разве mapM Haskell не ленив?

ОБНОВЛЕНИЕ: Хорошо этот вопрос становится потенциально очень простым.

q <- mapM return [1..]

Почему это никогда не возвращается?


mapM лениво не имеет дело с бесконечными списками?

Код ниже зависает. Однако, если я заменяю строку с методической точностью B, она больше не зависает. С другой стороны, если я предшествую строке "splitRandom $", это также не зависает.

Q1: разве mapM не ленив? Иначе, почему замена выравнивает со строкой B, "исправляют этот" код?

Q2: Почему делает предыдущую строку с splitRandom, "решают" проблему?

import Control.Monad.Random
import Control.Applicative

f :: (RandomGen g) => Rand g (Double, [Double])
f = do
    b <- splitRandom $ sequence $ repeat $ getRandom
    c <- mapM return b -- A
    -- let c = map id b -- B
    a <- getRandom
    return (a, c)

splitRandom :: (RandomGen g) => Rand g a -> Rand g a
splitRandom code = evalRand code <$> getSplit

t0 = do
    (a, b) <- evalRand f <$> newStdGen
    print  a
    print (take 3 b)

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

Однако, если я применяю mapM к списку, программа теперь зависает. Для предотвращения этого зависания я должен применить splitRandom снова перед mapM. У меня создалось впечатление, что mapM может лениво

18
задан Don Stewart 20 April 2011 в 22:50
поделиться

2 ответа

Ну, есть ленивый, а есть ленивый . mapM действительно ленив в том смысле, что не выполняет больше работы, чем должен.Однако посмотрите на сигнатуру типа:

mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]

Подумайте, что это означает: вы даете ему функцию a -> m b и набор a s. Обычная карта может превратить их в группу из m b s, но не в m [b] . Единственный способ объединить b в один [b] , не мешая монаде, - это использовать >> = для последовательности mb вместе, чтобы построить список .

Фактически, mapM в точности эквивалентна последовательности . карта .

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

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

data Stream m a = Nil | Stream a (m (Stream m a))

... так что вы заставляете только столько монадных слоев, сколько необходимо.

Edit :: Что касается splitRandom , то здесь происходит то, что вы передаете ему вычисление Rand , оценивая его с помощью seed splitRandom получает, затем возвращает результат.Без splitRandom начальное число, используемое одиночным getRandom , должно происходить из конечного результата упорядочивания бесконечного списка, поэтому оно зависает. С дополнительным splitRandom , используемому начальному числу нужно только распределять поток через два вызова splitRandom , поэтому он работает. Окончательный список случайных чисел работает, потому что вы покинули монаду Rand в этот момент, и от ее конечного состояния ничего не зависит.

32
ответ дан 30 November 2019 в 06:25
поделиться

Это попытка доказательства того, что mapM return [1 ..] не завершается. Предположим на время, что мы находимся в монаде Identity (аргумент применим и к любой другой монаде):

mapM return [1..] -- initial expression
sequence (map return [1 ..]) -- unfold mapM
let k m m' = m >>= \x ->
             m' >>= \xs ->
             return (x : xs)
in foldr k (return []) (map return [1..]) -- unfold sequence

Пока все хорошо ...

-- unfold foldr
let k m m' = m >>= \x ->
             m' >>= \xs ->
             return (x : xs)
    go [] = return []
    go (y:ys) = k y (go ys)
in go (map return [1..])

-- unfold map so we have enough of a list to pattern-match go:
go (return 1 : map return [2..])
-- unfold go:
k (return 1) (go (map return [2..])
-- unfold k:
(return 1) >>= \x -> go (map return [2..]) >>= \xs -> return (x:xs)

Вспомните, что ] return a = Identity a в монаде Identity и (Identity a) >> = f = fa в монаде Identity. Продолжение:

-- unfold >>= :
(\x -> go (map return [2..]) >>= \xs -> return (x:xs)) 1
-- apply 1 to \x -> ... :
go (map return [2..]) >>= \xs -> return (1:xs)
-- unfold >>= :
(\xs -> return (1:xs)) (go (map return [2..]))

Обратите внимание, что сейчас мы хотели бы подать заявку на \ xs , но пока не можем! Вместо этого мы должны продолжить развертывание, пока не получим значение, которое нужно применить:

-- unfold map for go:
(\xs -> return (1:xs)) (go (return 2 : map return [3..]))
-- unfold go:
(\xs -> return (1:xs)) (k (return 2) (go (map return [3..])))
-- unfold k:
(\xs -> return (1:xs)) ((return 2) >>= \x2 ->
                         (go (map return [3..])) >>= \xs2 ->
                         return (x2:xs2))
-- unfold >>= :
(\xs -> return (1:xs)) ((\x2 -> (go (map return [3...])) >>= \xs2 ->
                        return (x2:xs2)) 2)

На данный момент мы все еще не можем применить к \ xs , но мы можем применить к \ x2 . Продолжение:

-- apply 2 to \x2 :
(\xs -> return (1:xs)) ((go (map return [3...])) >>= \xs2 ->
                         return (2:xs2))
-- unfold >>= :
(\xs -> return (1:xs)) (\xs2 -> return (2:xs2)) (go (map return [3..]))

Теперь мы дошли до точки, когда ни \ xs , ни \ xs2 еще нельзя уменьшить! Наш единственный выбор:

-- unfold map for go, and so on...
(\xs -> return (1:xs))
  (\xs2 -> return (2:xs2))
    (go ((return 3) : (map return [4..])))

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

Это имеет смысл, если вы посмотрите на этот пример (заимствованный из другого потока StackOverflow, я не могу найти в настоящий момент).В следующем списке монад:

mebs = [Just 3, Just 4, Nothing]

мы ожидаем, что последовательность перехватит Nothing и вернет ошибку для всего этого:

sequence mebs = Nothing

Однако для этого списка:

mebs2 = [Just 3, Just 4]

мы ожидаем, что последовательность даст нам:

sequence mebs = Just [3, 4]

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

Примечание: В предыдущей версии этого ответа утверждалось, что foldr вычисляет, начиная с конца списка, и вообще не будет работать с бесконечными списками, но это неверно! Если оператор, который вы передаете в foldr , ленив по своему второму аргументу и выдает вывод с помощью ленивого конструктора данных, такого как список, foldr с радостью будет работать с бесконечным списком. См. Пример foldr (\ x xs -> (дублировать x x) ++ xs) [] [1 ...] . Но это не относится к нашему оператору k .

7
ответ дан 30 November 2019 в 06:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: