Ответ 1
Тот же трюк, что и в потоке, - не захватывает остаток напрямую, а вместо этого фиксирует значение и функцию, которая дает остаток. При необходимости вы можете добавить напоминание поверх этого.
data UTree a = Leaf a | Branch a (a -> [UTree a])
Я не в настроении разобраться с этим именно сейчас, но эта структура возникает, я уверен, естественно, как cofree comonad над довольно простым функтором.
Edit
Или это, возможно, проще понять: 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
В приведенном ниже коде показано, как это может предотвратить обмен. Обратите внимание, что даже в версии без совместного использования в профиле есть горбы, указывающие, что вызовы суммы и длины не работают в постоянном пространстве. Я бы предположил, что нам нужно четкое аккуратное накопление, чтобы сбить их.
{-# 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)