Le code suivant crée une arborescence infinie, tout en créant simultanément un cache de tous les sous-arborescences, de sorte qu'aucun sous-arborescence en double ne soit créé. La raison d'être de l'élimination des sous-arbres en double provient de l'application aux arbres d'états des jeux de type échecs: on peut souvent se retrouver dans le même état de jeu en changeant simplement l'ordre de deux coups. Au fur et à mesure que le jeu progresse, les états qui deviennent inaccessibles ne devraient pas continuer à occuper la mémoire. Je pensais pouvoir résoudre ce problème en utilisant des pointeurs faibles. Malheureusement, l'utilisation de pointeurs faibles nous amène dans la Mono IO et cela semble avoir détruit assez/toute paresse de sorte que ce code ne se termine plus.Comment créer une arborescence infinie avec élimination des doublons via le cache des pointeurs faibles dans Haskell
Ma question est la suivante: est-il possible de générer efficacement un arbre paresseux (état de jeu) sans sous-arborescence en double (et sans fuites de mémoire)?
{-# 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!!!"
Je ne pense pas que les pointeurs faibles (donc 'IO') seront nécessaires. Par exemple, 'Data.Seq' se donne beaucoup de mal pour maximiser le partage interne en utilisant un code intelligent: https://hackage.haskell.org/package/containers-0.5.7.1/docs/src/Data.Sequence.html#applicativeTree – cdk
@cdk, je n'arrive pas à trouver où/comment ça fonctionne. Du commentaire "Note spéciale: la spécialisation Identité fait automatiquement le partage des nœuds, réduisant l'utilisation de la mémoire de l'arborescence résultante à/O (log n) /", il semble implicite que le code n'implémente pas explicitement le partage, mais les optimisations du compilateur pour une spécialisation spécifique, faites-le de toute façon (ce qui implique que pour d'autres cas cela ne se produira pas). Pourriez-vous expliquer un peu plus comment Data.Seq partage et comment cela m'aiderait? – hkBst
@hkBst, ce commentaire est trompeur, et je vais essayer de le modifier pour clarifier dans la prochaine version. La spécialisation du compilateur à 'Identity' ne fait qu'améliorer les facteurs constants. Qu'est-ce que cela signifie vraiment, c'est que * lorsqu'il est utilisé avec «Identity» * il y a beaucoup de partage. – dfeuer