【发布时间】:2013-04-18 15:16:07
【问题描述】:
我正在编写一个程序,它创建一个 shell 脚本,其中包含一个用于目录中每个图像文件的命令。目录中有667,944张图片,所以我需要妥善处理严格/懒惰的问题。
这是一个简单的例子,它给了我Stack space overflow。如果我使用+RTS -Ksize -RTS 给它更多空间,它确实可以工作,但它应该能够以很少的内存运行,立即产生输出。所以我一直在阅读 Haskell wiki 和有关 Haskell 的 wikibook 中有关严格性的内容,试图找出解决问题的方法,我认为这是给我的 mapM 命令之一悲痛,但我对严格排序问题的理解仍然不够。
我在 SO 上发现了一些其他似乎相关的问题(Is mapM in Haskell strict? Why does this program get a stack overflow? 和 Is Haskell's mapM not lazy?),但我仍然无法获得启发。
import System.Environment (getArgs)
import System.Directory (getDirectoryContents)
genCommand :: FilePath -> FilePath -> FilePath -> IO String
genCommand indir outdir file = do
let infile = indir ++ '/':file
let angle = 0 -- have to actually read the file to calculate this for real
let outfile = outdir ++ '/':file
return $! "convert " ++ infile ++ " -rotate " ++ show angle ++
" -crop 143x143+140+140 " ++ outfile
main :: IO ()
main = do
putStrLn "#!/bin/sh"
(indir:outdir:_) <- getArgs
files <- getDirectoryContents indir
let imageFiles = filter (`notElem` [".", ".."]) files
commands <- mapM (genCommand indir outdir) imageFiles
mapM_ putStrLn commands
编辑:测试#1
这是示例的最新版本。
import System.Environment (getArgs)
import System.Directory (getDirectoryContents)
import Control.Monad ((>=>))
genCommand :: FilePath -> FilePath -> FilePath -> IO String
genCommand indir outdir file = do
let infile = indir ++ '/':file
let angle = 0 -- have to actually read the file to calculate this for real
let outfile = outdir ++ '/':file
return $! "convert " ++ infile ++ " -rotate " ++ show angle ++
" -crop 143x143+140+140 " ++ outfile
main :: IO ()
main = do
putStrLn "TEST 1"
(indir:outdir:_) <- getArgs
files <- getDirectoryContents indir
putStrLn $ show (length files)
let imageFiles = filter (`notElem` [".", ".."]) files
-- mapM_ (genCommand indir outdir >=> putStrLn) imageFiles
mapM_ (\filename -> genCommand indir outdir filename >>= putStrLn) imageFiles
我使用命令ghc --make -O2 amy2.hs -rtsopts 编译它。如果我使用命令./amy2 ~/nosync/GalaxyZoo/table2/images/ wombat 运行它,我会得到
TEST 1
Stack space overflow: current size 8388608 bytes.
Use `+RTS -Ksize -RTS' to increase it.
如果我改为使用命令 ./amy2 ~/nosync/GalaxyZoo/table2/images/ wombat +RTS -K20M 运行它,我会得到正确的输出...最终:
TEST 1
667946
convert /home/amy/nosync/GalaxyZoo/table2/images//587736546846572812.jpeg -rotate 0 -crop 143x143+140+140 wombat/587736546846572812.jpeg
convert /home/amy/nosync/GalaxyZoo/table2/images//587736542558617814.jpeg -rotate 0 -crop 143x143+140+140 wombat/587736542558617814.jpeg
...等等。
【问题讨论】:
-
genCommand是否实际上首先进行了任何 I/O?如果map可以工作,为什么还要使用mapM? -
在这个例子中,genCommand 实际上并没有做任何 IO。但在我的实际应用程序中,genCommand 将读取文件以计算适当的参数以在它生成的命令中使用。所以我想我需要使用 mapM。
-
尝试用来自stackoverflow.com/questions/15546216/…的
safeMapM替换mapM -
另一种可能的解决方案是使用一些迭代库(例如
pipes或conduit在生成列表元素时对其进行处理)。 -
您还需要流式传输文件名列表,否则会泄漏空间。
标签: haskell lazy-evaluation strictness