Я намагаюся розібратися, як рекурсія в поєднанні зі Списком Зрозуміння / Монадічними операціями викликає витоки простору.
У мене невелика програма тестування:
module Main where
permute :: [a] -> Integer -> [[a]]
permute _ 0 = [[]]
permute xs" n = [x:xs | x <- xs", xs <- permute xs" (n-1)]
chars1 = ["0".."9"]
main = do
putStrLn $ (permute chars1 10000)!!100000000 -- Leaks
print $ [1..]!!100000000 -- Does not leak
Тепер я розумію, що функція permute
можна розширити як
xs" >>= x -> (permute xs" (n-1) >>= xs -> (x:xs))
Так багато (permute xs" n)
буде складено до того, як результат можна буде отримати. Я правильно розумію? Якщо так, що я можу зробити, щоб функція не просочилася?
Відповіді:
2 для відповіді № 1Цей код трохи дивний, тому що permute
насправді не так .. добре .. перестановляйте речі, але припускаючи, що це те, що ви задумали, насправді виконує так само, як і ваш [0..] !! 10000
Наприклад, просто те, що обчислення 100-тисячного вибору - це набагато більше роботи у вашому коді!
Важливим є те, що ваш код є ледачий, ми можемо легко обчислити 1-й елемент permute [0..] 10
навіть якщо їх явно нескінченно багато.
Можливо, ви можете думати, що коли ви реально запускаєте це, потрібно зробити дуже багато часу будь-який вихід, навіть якщо ви можете сподіватися, що це будевиробляти 1-го символу ... трохи почекати .. виробляти другий символ .. трохи почекати .. і так далі. Це насправді не є "витоком" в сенсі Хаскелла, і цього сценарію неможливо уникнути. Ваш список по суті побудований з
map ("0":) (permute chars (n - 1)) ++ ... ++ map ("9":) (permute chars (n - 1))
Проблема полягає в тому, що для того, щоб знати першого символу, вам потрібно розібратися, з якого «блоку» ви будете вибирати, що передбачає обчислення довжини блоку, що передбачає оцінку permute chars (n - 1)
повністю. Отже, що "у вас трапиться - ви змушуватимете кожен рекурсивний дзвінок по черзі, поки у вас не буде достатньо елементів у списку, що випливає, для оцінки дзвінка !!
. Це, однак, не несподівано. Однак ви можете значно пришвидшити цей код одним досить простим трюком: перевернути його x
і xs
у списку осягнення.
permute :: [a] -> Integer -> [[a]]
permute _ 0 = [[]]
permute xs" n = [x:xs | xs <- permute xs" (n-1), x <- xs"]
Зверніть увагу, що збільшення обміну могло б призвести дотеорія призводить до збільшення використання пам'яті (щось не може бути GCed як тільки в цій версії), але я не в змозі придумати не абсурдно надуману програму, яка це робить.