gpt4 book ai didi

performance - 有什么方法可以创建 unmemo-monad?

转载 作者:行者123 更新时间:2023-12-03 09:36:00 25 4
gpt4 key购买 nike

假设有人编写了一个程序来下棋或解决数独问题。在这种程序中,有一个表示游戏状态的树结构是有意义的。

这棵树会很大,“几乎是无限的”。这本身不是问题,因为 Haskell 支持无限数据结构。

一个熟悉的无限数据结构示例:

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

仅在第一次使用时才分配节点,因此列表占用有限的内存。如果他们不保留对其头部的引用,也可以迭代无限列表,从而允许垃圾收集器收集不再需要的部分。

回到树的例子——假设对树进行了一些迭代,如果树的根仍然需要,则迭代的树节点可能不会被释放(例如在迭代深化搜索中,树将被迭代多次所以需要保留根)。

我想到的这个问题的一个可能的解决方案是使用“unmemo-monad”。

我将尝试使用 monadic 列表来演示这个 monad 应该做什么:
import Control.Monad.ListT (ListT)  -- cabal install List
import Data.Copointed -- cabal install pointed
import Data.List.Class
import Prelude hiding (enumFromTo)

nums :: ListT Unmemo Int -- What is Unmemo?
nums = enumFromTo 0 1000000

main = print $ div (copoint (foldlL (+) 0 nums)) (copoint (lengthL nums))

使用 nums :: [Int] , 程序会占用大量内存作为对 nums 的引用 lengthL nums 需要在迭代 foldlL (+) 0 nums 时.
Unmemo的目的|是使运行时不保持节点迭代。

我尝试使用 ((->) ())Unmemo , 但它产生与 nums :: [Int] 相同的结果确实 - 程序使用了大量内存,通过 +RTS -s 运行它就可以看出这一点。 .

有没有办法实现 Unmemo那是我想要的吗?

最佳答案

与流相同的技巧 - 不要直接捕获余数,而是捕获一个值和一个产生余数的函数。您可以根据需要在此之上添加内存。

data UTree a = Leaf a | Branch a (a -> [UTree a]) 

我现在没有心情准确地弄清楚它,但我敢肯定,这个结构自然地作为一个相当简单的仿函数上的 cofree 共单子(monad)出现了。

编辑

找到它: http://hackage.haskell.org/packages/archive/comonad-transformers/1.6.3/doc/html/Control-Comonad-Trans-Stream.html

或者这可能更容易理解: http://hackage.haskell.org/packages/archive/streams/0.7.2/doc/html/Data-Stream-Branching.html

无论哪种情况,诀窍在于您的 f可以选择为 data N s a = N (s -> (s,[a]))一个合适的 s (s 是流的状态参数的类型——如果你愿意的话,是展开的种子)。这可能不完全正确,但应该做一些接近的事情......

但当然,对于实际工作,您可以放弃所有这些,直接像上面那样编写数据类型。

编辑 2

下面的代码说明了这如何防止共享。请注意,即使在没有共享的版本中,配置文件中也有驼峰,表明 sum 和 length 调用没有在恒定空间中运行。我想我们需要一个明确的严格积累来击倒这些。
{-# LANGUAGE DeriveFunctor #-}
import Data.Stream.Branching(Stream(..))
import qualified Data.Stream.Branching as S
import Control.Arrow
import Control.Applicative
import Data.List

data UM s a = UM (s -> Maybe a) deriving Functor
type UStream s a = Stream (UM s) a

runUM s (UM f) = f s
liftUM x = UM $ const (Just x)
nullUM = UM $ const Nothing

buildUStream :: Int -> Int -> Stream (UM ()) Int
buildUStream start end = S.unfold (\x -> (x, go x)) start
where go x
| x < end = liftUM (x + 1)
| otherwise = nullUM

sumUS :: Stream (UM ()) Int -> Int
sumUS x = S.head $ S.scanr (\x us -> maybe 0 id (runUM () us) + x) x

lengthUS :: Stream (UM ()) Int -> Int
lengthUS x = S.head $ S.scanr (\x us -> maybe 0 id (runUM () us) + 1) x

sumUS' :: Stream (UM ()) Int -> Int
sumUS' x = last $ usToList $ liftUM $ S.scanl (+) 0 x

lengthUS' :: Stream (UM ()) Int -> Int
lengthUS' x = last $ usToList $ liftUM $ S.scanl (\acc _ -> acc + 1) 0 x

usToList x = unfoldr (\um -> (S.head &&& S.tail) <$> runUM () um) x

maxNum = 1000000
nums = buildUStream 0 maxNum

numsL :: [Int]
numsL = [0..maxNum]

-- All these need to be run with increased stack to avoid an overflow.

-- This generates an hp file with two humps (i.e. the list is not shared)
main = print $ div (fromIntegral $ sumUS' nums) (fromIntegral $ lengthUS' nums)

-- This generates an hp file as above, and uses somewhat less memory, at the cost of
-- an increased number of GCs. -H helps a lot with that.
-- main = print $ div (fromIntegral $ sumUS nums) (fromIntegral $ lengthUS nums)

-- This generates an hp file with one hump (i.e. the list is shared)
-- main = print $ div (fromIntegral $ sum $ numsL) (fromIntegral $ length $ numsL)

关于performance - 有什么方法可以创建 unmemo-monad?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6208006/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com