Карта HaskellM не ленива? - PullRequest
       66

Карта HaskellM не ленива?

17 голосов
/ 17 июля 2010

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

q <- mapM return [1..]

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


Разве mapM не лениво обрабатывает бесконечные списки?

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

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

Q2: Почему предшествующая строка A с 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 может лениво

Ответы [ 5 ]

31 голосов
/ 17 июля 2010

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

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

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

Фактически, mapM точно соответствует sequence . map.

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

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

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

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

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

7 голосов
/ 06 января 2013

Хорошо, этот вопрос потенциально очень прост.

q <- mapM return [1 ..] </p>

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

Это не обязательно правда. Это зависит от вашей монады.

Например, с монадой идентичности вы можете использовать результат лениво, и он заканчивается нормально:

newtype Identity a = Identity a

instance Monad Identity where
  Identity x >>= k = k x
  return = Identity

-- "foo" is the infinite list of all the positive integers
foo :: [Integer]
Identity foo = do
  q <- mapM return [1..]
  return q

main :: IO ()
main = print $ take 20 foo -- [1 .. 20]
7 голосов
/ 18 июля 2010

Вот попытка доказать, что 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 = f a в монаде 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]

мы ожидаем, что sequence поймает Nothing и вернет ошибку в целом:

sequence mebs = Nothing

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

mebs2 = [Just 3, Just 4]

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

sequence mebs = Just [3, 4]

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

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

3 голосов
/ 11 января 2015

Этот вопрос очень хорошо показывает разницу между монадой IO и другими монадами. В фоновом режиме mapM создает выражение с операцией связывания (>> =) между всеми элементами списка, чтобы превратить список монадических выражений в монадическое выражение списка. Теперь, что отличается в монаде IO, так это то, что модель исполнения Haskell выполняет выражения во время привязки в монаде IO. Это именно то, что в конечном итоге заставляет (в чисто ленивом мире) что-либо вообще выполнять.

Таким образом, IO Monad является особым в некотором смысле, он использует парадигму последовательности bind для фактического обеспечения выполнения каждого шага, и это то, что наша программа делает для выполнения чего-либо вообще в конце. Другие монады разные. У них есть другие значения оператора связывания, в зависимости от монады. На самом деле IO - это та монада, которая выполняет вещи в привязке, и именно поэтому типы IO являются «действиями».

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

Есть хорошие статьи об этом, начните здесь или ищите денотационную семантику и монады: Борьба с неловким составом: http://research.microsoft.com/en-us/um/people/simonpj/papers/marktoberdorf/mark.pdf

Пример с Maybe Monad:

модуль Main где

fstMaybe :: [Int] -> Maybe [Int] fstMaybe = mapM (\ x -> если x == 3, то ничего больше x)

main = do let r = fstMaybe [1 ..] возврат r

1 голос
/ 14 июля 2014

Давайте поговорим об этом в более общем контексте.

Как сказано в других ответах, mapM - это просто комбинация sequence и map. Таким образом, проблема в том, почему sequence строго в определенных Monad с. Однако это не ограничивается Monads, но также Applicative с, поскольку у нас есть sequenceA, которые в большинстве случаев используют одну и ту же реализацию sequence.

Теперь посмотрите на (специализированную для списков) подпись типа sequenceA:

sequenceA :: Applicative f => [f a] -> f [a]

Как бы вы это сделали? Вам дали список, поэтому вы хотели бы использовать foldr в этом списке.

sequenceA = foldr f b where ...
  --f :: f a -> f [a] -> f [a]
  --b :: f [a]

Поскольку f является Applicative, вы знаете, что такое b coule - pure []. Но что такое f? Очевидно это поднятая версия (:):

(:) :: a -> [a] -> [a]

Итак, теперь мы знаем, как sequenceA работает:

sequenceA = foldr f b where
  f a b = (:) <$> a <*> b
  b = pure []

или

sequenceA = foldr ((<*>) . fmap (:)) (pure [])

Предположим, вы получили ленивый список (x:_|_). Вышеприведенное определение sequenceA дает

sequenceA (x:_|_) === (:) <$> x <*> foldr ((<*>) . fmap (:)) (pure []) _|_
                  === (:) <$> x <*> _|_

Итак, теперь мы видим, что проблема была уменьшена с учетом погоды f <*> _|_ - это _|_ или нет. Очевидно, что если f строгое, то это _|_, но если f не строгое, чтобы допустить остановку оценки, мы требуем, чтобы сам <*> был нестрогим.

Таким образом, критерии для аппликативного функтора с sequenceA, который останавливается, будут оператор <*> не является строгим. Простой тест будет

const a <$> _|_ === _|_      ====> strict sequenceA
-- remember f <$> a === pure f <*> a

Если мы говорим о Moand с, то критерии

_|_ >> const a === _|_ ===> strict sequence
...