Pointers 对相同数据和内存分配的引用 请考虑以下数据模型: data Artist = Artist Text data Song = Song Artist Text data Catalogue = Catalogue (Set Artist) (Set Song)
您可以看到Pointers 对相同数据和内存分配的引用 请考虑以下数据模型: data Artist = Artist Text data Song = Song Artist Text data Catalogue = Catalogue (Set Artist) (Set Song),pointers,haskell,memory,Pointers,Haskell,Memory,您可以看到艺术家s是从歌曲s和目录中引用的。目录包含从歌曲s中引用的所有艺术家的列表,因此艺术家的相同值可以从两个地方引用 假设我们使用以下函数的多个应用程序生成目录值: insertSong :: Song -> Catalogue -> Catalogue insertSong song@(Song artist title) (Catalogue artists songs) = Catalogue (Set.insert artist artists) (Set.inse
艺术家
s是从歌曲
s和目录
中引用的。目录
包含从歌曲
s中引用的所有艺术家的列表,因此艺术家
的相同值可以从两个地方引用
假设我们使用以下函数的多个应用程序生成目录
值:
insertSong :: Song -> Catalogue -> Catalogue
insertSong song@(Song artist title) (Catalogue artists songs) =
Catalogue (Set.insert artist artists) (Set.insert song songs)
显然,目录
将通过引用与歌曲
相同的艺术家
值来填充,从而通过不存储这些值的副本来节省内存
问题是,当我试图通过分别对一组艺术家和一组歌曲进行反序列化,从序列化数据重新创建目录时,应用程序占用的内存要比使用insertSong
生成相同的catalog
值时多得多。我怀疑这是由于从歌曲
s中引用的同一艺术家
s与目录
之间的关系丢失造成的,这就是为什么我得到了艺术家
的值副本,占用了额外的内存
我看到的唯一解决方案是首先反序列化艺术家集,然后反序列化歌曲集,同时强制将Artist
的值替换为第一组中的值
因此,我的问题是:
请注意,如果您对字符串进行任何类型的计算,共享也将丢失(即,即使
artist1
和artist2
相同且共享,f artist1
和f artist2
可能不相同)。如果这成为一个问题,您也可以对数据结构进行类似的更改。一个简单的解决方案似乎是使用某种退化的映射缓存数据:
{-# LANGUAGE DeriveDataTypeable, RankNTypes #-}
import Control.Monad
import Control.Monad.State
import Data.Map (Map)
import qualified Data.Map as M
type Cache a = Map a a
然后,如果已经存在与此缓存相同的条目,我们可以查询此缓存,并将其替换为缓存的条目:
cached :: (Ord a) => a -> State (Cache a) a
cached x = state $ \m ->
case M.lookup x m of
Just x' -> (x', m)
Nothing -> (x, M.insert x x m)
这样,如果我们加载几个类型为a
的相等元素,我们将它们转换为单个元素。这可以在反序列化过程中完成,也可以在最后完成一次
也许可以进一步推广它,并使用SYB通过缓存映射数据结构中某些给定类型的所有值:
import Data.Data (Data)
import Data.Generics.Aliases (mkM)
import Data.Generics.Schemes (everywhereM)
import Data.Typeable (Typeable)
replaceFromCache
:: (Ord a, Typeable a, Data b)
=> b -> State (Cache a) b
replaceFromCache = everywhereM (mkM cached)
然后我们可以替换某些数据结构中的所有艺术家,如
data Artist = Artist String
deriving (Eq, Ord, Typeable)
cacheAllArtists :: (Data b) => b -> b
cacheAllArtists b = evalState (replaceFromCache b) (M.empty :: Cache Artist)
或者我们可以使用phantom type创建通用版本:
cacheAll :: (Ord a, Typeable a, Data b)
=> Proxy a -> b -> b
cacheAll p = flip evalState (emptyOf p) . replaceFromCache
where
emptyOf p = asTypeOf2 M.empty p
asTypeOf2 :: f a -> Proxy a -> f a
asTypeOf2 = const
cacheAllArtists :: (Data b) => b -> b
cacheAllArtists = cacheAll (Proxy :: Proxy Artist)
(免责声明:我没有测试上面的任何代码。)我偶然发现了一个项目,它解决了这个问题。参见。关于泛型的想法非常有趣。这个问题对于开发这样一个图书馆来说已经足够普遍了。谢谢我会调查的。@NikitaVolkov我很乐意参与。太好了!虽然我必须承认,我还没有准备好参与这样一个项目,但如果我能回到这个项目,我会与你保持联系。我偶然发现了一个项目,它接近这个问题。看见