2010-07-17 4 views
17

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

q <- mapM return [1..] 

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


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

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

Q1: Является ли mapM не ленивым? В противном случае, почему замена строки A на строку B «исправить этот» код?

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

ответ

31

Ну, есть ленивый, а затем есть ленивый. 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.

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

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

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

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

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

+0

Но это не объясняет, почему предыдущая строка с splitRandom решает проблему.Также обратите внимание, что splitRandom фактически разбивает случайное семя на два и использует один из них для создания бесконечного списка, поэтому там не должно быть зависимостей. – qrest

+0

@qrest: Добавлено дополнительное примечание - это помогает? –

+0

@camccann: Нет, это не так. Вот мои рассуждения. Мне нужно использовать одно семя для создания бесконечного списка. Следовательно, я разделил семя и использовал его в «splitRandom $ sequence $ repeat $ getRandom». ПРИМЕЧАНИЕ. Это монодичное выражение, и оно не оценивалось полностью! Обратите внимание, что я целенаправленно получил случайное число для «a» ПОСЛЕ бесконечного списка, доказывая, что семена не зависят. И монашеская монада не использует семя, если не генерируется случайное число, поэтому не должно существовать зависимость от семян. Итак, «вернуть» тот, который фактически заставляет оценивать? – qrest

7

Проводите попытку доказать, что 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 a) >>= f = f a в Монастыре идентичности. Продолжение:

-- 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.

+1

Сказать, что 'foldr' работает" из задней части списка "немного запутанно. И 'foldl', и' foldr' обрабатывают список слева направо. Разница заключается в * брекетинге *, а не в направлении обработки. – nh2

+1

Я бы сказал, что ваше описание - это нечто более запутанное. В самой документации Prelude сказано, что 'foldr'" сокращает список, используя двоичный оператор справа налево "- брекетинг - это целая точка! Начальный левый-правый проходит через список (обязательно в любом случае, чтобы пересечь структуру списка Haskell) устанавливает стек рекурсии, но не вычисляет элементы списка. –

+0

Я не согласен: до конца нет «начального левого правого прохода». Если бы это было так, как бы 'foldr' работал над бесконечными списками? 'foldr (:) [] [1 ..]' работает отлично, а постоянная память - этого не может быть, если список обрабатывается справа налево, потому что нет правого конца. – nh2

7

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

< д - mapM возвращение [1 ..]

Почему же это не вернется?

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

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

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] 
1

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

Как и другие ответы, 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 
3

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

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

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

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

Примера с Maybe монады:

модулем Main где

fstMaybe: : [Int] -> Может быть [Int] fstMaybe = mapM (\ x -> if x == 3 then Nothing else Just x)

main = do let r = fstMaybe [1 ..] return r