所以我试图找到使用数字15
制作数字5的方法import Data.List
import Control.Monad
import Control.Monad.Omega
import System.IO
data Expr = X | Sub Expr Expr | Div Expr Expr | Pow Expr Expr
deriving Show
eval x X = x
eval x (Sub a b) = eval x a - eval x b
eval x (Div a b) = eval x a / eval x b
eval x (Pow a b) = eval x a ** eval x b
exprs | v <- each exprs
= (X:) . concat $ transpose
[ runOmega $ liftM2 Div v v
, runOmega $ liftM2 Sub v v
, runOmega $ liftM2 Pow v v
]
main = do
hSetBuffering stdout LineBuffering
mapM_ print $ filter ((==5).eval 15) exprs
但是,第一个结果是列表中的118588079th元素,并且Haskell在到达之前很久就耗尽了内存。我怎么知道第一个是118588079?因为如果我只计算它并要求索引,Haskell根本就不使用内存:
run x | v <- each $ run x
= (x:) . concat $ transpose
[ runOmega $ liftM2 (/ ) v v
, runOmega $ liftM2 (- ) v v
, runOmega $ liftM2 (**) v v
]
main = print . map snd . filter ((==5).fst) $ zip (run 15) [0..]
在第一种情况下我的记忆究竟在哪里,我该如何解决它?
答案 0 :(得分:5)
顶级绑定,如
list = [1..1000000]
将懒惰地生成一次列表,并将其保存在内存中以供后续使用。
一个功能,而不是
fun x = [1..1000000]
会在每次通话时分配一个新列表,每次都从头开始重新计算(懒惰)。因为它是由顶级绑定引用的,所以它永远不会被垃圾收集。
请注意,这不是Haskell规定的 - 它只是以这种方式工作的GHC。
为了进行比较,请尝试以下变体:
run x | v <- each $ run x
= (x:) . concat $ transpose
[ runOmega $ liftM2 (/ ) v v
, runOmega $ liftM2 (- ) v v
, runOmega $ liftM2 (**) v v
]
run15 = run 15
main = print . map snd . filter ((==5).fst) $ zip run15 [0..]
您应该会看到大量内存被消耗,因为不会发生垃圾回收。取而代之的是,
main = print . map snd . filter ((==5).fst) $ zip run15 [0..]
where
run15 = run 15
应该允许垃圾收集并在少量内存中运行。
(顺便说一下,使用模式绑定代替let / where困惑了我一段时间。)
答案 1 :(得分:2)
@chi是完全正确的。没有参数的函数实际上不是一个函数,并且将保存在内存中,无论它是如何递归的,它是否产生无限的数据结构。这会导致你的内存不足:
run' | v <- each run'
= (15:) . concat $ transpose
[ runOmega $ liftM2 (/ ) v v
, runOmega $ liftM2 (- ) v v
, runOmega $ liftM2 (**) v v
]
main = print $ run' !! 1000000000
虽然第一个run
函数不会。同样的说明 - 尽管看起来很傻 - 这不会耗尽内存:
exprs' x | v <- each $ exprs' x
= (x:) . concat $ transpose
[ runOmega $ liftM2 Div v v
, runOmega $ liftM2 Sub v v
, runOmega $ liftM2 Pow v v
]
main = print $ exprs' X !! 1000000000
如果有人好奇,列表中的118588079个元素是((((x**x)/x)-(x**(x-(x/x))))-((x-x)-(x/x)))