Poniższy kod tworzy nieskończone drzewo, jednocześnie tworząc pamięć podręczną wszystkich poddrzew, tak aby nie tworzyć duplikatów poddrzew. Uzasadnieniem dla eliminacji zduplikowanych poddrzew jest zastosowanie do drzew stanów gier szachowych: często można skończyć w tym samym stanie gry, zmieniając kolejność dwóch ruchów. W trakcie gry stany, które stają się niedostępne, nie powinny nadal zajmować pamięci. Pomyślałem, że mogę rozwiązać ten problem poprzez użycie słabych wskaźników. Niestety używanie słabych wskaźników prowadzi nas do IO Monad i wydaje się, że zniszczyło to wystarczająco/wszystkie lenistwo, tak że ten kod już się nie kończy.Jak zbudować nieskończone drzewo z podwójną eliminacją poprzez pamięć podręczną słabych wskaźników w Haskell
Moje pytanie brzmi: Czy możliwe jest wydajne generowanie drzewa leniwego (stanu gry) bez duplikatów poddrzew (i bez przecieków pamięci)?
{-# LANGUAGE RecursiveDo #-}
import Prelude hiding (lookup)
import Data.Map.Lazy (Map, empty, lookup, insert)
import Data.List (transpose)
import Control.Monad.State.Lazy (StateT(..))
import System.Mem.Weak
import System.Environment
type TreeCache = Map Integer (Weak NTree)
data Tree a = Tree a [Tree a]
type Node = (Integer, [Integer])
type NTree = Tree Node
getNode (Tree a _) = a
getVals = snd . getNode
makeTree :: Integer -> IO NTree
makeTree n = fst <$> runStateT (makeCachedTree n) empty
makeCachedTree :: Integer -> StateT TreeCache IO NTree
makeCachedTree n = StateT $ \s -> case lookup n s of
Nothing -> runStateT (makeNewTree n) s -- makeNewTree n s
Just wt -> deRefWeak wt >>= \mt -> case mt of
Nothing -> runStateT (makeNewTree n) s
Just t -> return (t,s)
makeNewTree :: Integer -> StateT TreeCache IO NTree
makeNewTree n = StateT $ \s -> mdo
wt <- mkWeak n t Nothing
(ts, s') <- runStateT
(mapM makeCachedTree $ children n)
(insert n wt s)
let t = Tree (n, values n $ map getVals ts) ts
return (t, s')
children n = let bf = 10 in let hit = 2 in [bf*n..bf*n+bf+hit-1]
values n [] = repeat n
values n nss = n:maximum (transpose nss)
main = do
args <- getArgs
let n = read $ head args in
do t <- makeTree n
if length args == 1 then putStr $ show $ take (fromInteger n) $ getVals t else putStr "One argument only!!!"
Nie sądzę, że słabe wskaźniki (stąd "IO") będą konieczne. Na przykład 'Data.Seq' idzie na wielką długość, aby zmaksymalizować wewnętrzne udostępnianie za pomocą sprytnego kodu: https://hackage.haskell.org/package/containers-0.5.7.1/docs/src/Data.Sequence.html#applicativeTree – cdk
@cdk, nie mogę znaleźć gdzie/jak to działa. Z komentarza "Uwaga specjalna: specjalizacja Identity automatycznie udostępnia współużytkowanie węzłów, zmniejszając wykorzystanie pamięci wynikowego drzewa do/O (log n) /", wydaje się sugerować, że kod nie implementuje udostępniania jawnie, ale optymalizacje kompilatora w przypadku określonej specjalizacji tak się stanie (sugerując, że w innych przypadkach może się to nie zdarzyć). Czy możesz wyjaśnić nieco, w jaki sposób Data.Seq udostępnia i jak mi to pomoże? – hkBst
@ hkBst, ten komentarz wprowadza w błąd, a postaram się go edytować, aby wyjaśnić w następnej wersji. Specjalizacja kompilatora w "Tożsamości" nie ma nic oprócz poprawienia stałych czynników. Oznacza to, że * w przypadku użycia z 'Identity' * jest dużo udostępniania. – dfeuer