4
18
2012
5

Haskell 实战:惰性地读取子进程输出

本文来自依云's Blog,转载请注明。

突然想给 locate 命令写个 wrapper,把输出中的家目录和一些因加密而引入的软链接显示为~。自然,这需要读取 locate 命令的输出。在 process 这个库中看到了readProcess函数,似乎是自己想要的(完整代码):

readLocate :: [String] -> IO String
readLocate args = getArgs >>= \cmd ->
  let args' = args ++ cmd
  in readProcess "locate" args' ""

结果却发现,原本 locate 命令是边查找边输出的,现在变成了先静默,然后一下子全部吐出来。没有按 Haskell 惯常的「懒惰」脾气来。这样一来,当我发现输出项目太多想按Ctrl-C中断时已经晚了。

Google 了一下,找到这个

I guess people who want laziness can implement it themselves directly, taking care to get whatever laziness it is that they want.

好吧。我先下回 process 库的源码看看readProcess为什么不是惰性的:

readProcess 
    :: FilePath                 -- ^ command to run
    -> [String]                 -- ^ any arguments
    -> String                   -- ^ standard input
    -> IO String                -- ^ stdout
readProcess cmd args input = do
    (Just inh, Just outh, _, pid) <-
        createProcess (proc cmd args){ std_in  = CreatePipe,
                                       std_out = CreatePipe,
                                       std_err = Inherit }

    -- fork off a thread to start consuming the output
    output  <- hGetContents outh
    outMVar <- newEmptyMVar
    _ <- forkIO $ C.evaluate (length output) >> putMVar outMVar ()

    -- now write and flush any input
    when (not (null input)) $ do hPutStr inh input; hFlush inh
    hClose inh -- done with stdin

    -- wait on the output
    takeMVar outMVar
    hClose outh

    -- wait on the process
    ex <- waitForProcess pid

    case ex of
     ExitSuccess   -> return output
     ExitFailure r -> 
      ioError (mkIOError OtherError ("readProcess: " ++ cmd ++ 
                                     ' ':unwords (map show args) ++ 
                                     " (exit " ++ show r ++ ")")
                                 Nothing Nothing)

原来是另开了一 IO 线程读输出,然后等待进程结束后关闭管道。这解释为什么它不是惰性的——它得进程善后处理。

那好吧,改用createProcess好了:

doLocate :: IO (String, ProcessHandle)
doLocate = do
  argv0 <- getProgName
  let args = case argv0 of
                  "lre" -> ["-b", "--regex"]
                  _ -> []
  args' <- getArgs
  let args'' = args ++ args'
  (_, Just out, _, p) <- createProcess (proc "locate" args''){ std_in = Inherit,
                                                               std_out = CreatePipe,
                                                               std_err = Inherit }
  hSetBuffering out LineBuffering
  (,) <$> hGetContents out <*> return p

改进后的程序,不会等待进程结束,而是返回输出和进程句柄。进程句柄用来等待子进程结束,同时获取退出状态。至于那个管道就不关闭了,留给操作系统解决好了。

main = do
  (out, p) <- doLocate
  putStr $ transform out
  waitForProcess p >>= exitWith

改进版的完整程序在此

Category: Haskell | Tags: Haskell linux | Read Count: 9693
MaskRay 说:
Apr 21, 2012 09:01:44 AM

期待你再改一下 indet/haskell.vim ,
单行 where 缩进两格,下一行缩进两格
do 也是缩进两格

两格 = shiftwidth / 2

像 emacs 的 haskell-mode 那样

f = f
where
f = f

g = do
return f

Avatar_small
依云 说:
Apr 21, 2012 03:26:10 PM

我这里的 Emacs haskell-mode 里 where 缩进了四个空格呀。我的 shiftwidth 为 2,刚好和你想要的一样。再除以 2 的话就只剩下一个空格了。而 sw=3 的情况下怎么办呢?

MaskRay 说:
May 03, 2012 10:06:30 PM

催你更新……
{ xx
, yy
} 这个括号会被左移 shiftwidth 格

Avatar_small
依云 说:
May 04, 2012 03:36:48 PM

真麻烦啊……你能给我个完整的 Haskell 缩进规则之类的材料吗?
另外,你介意我使用 Python 脚本吗?

MaskRay 说:
May 04, 2012 10:11:30 PM

你有意向改真的太好了……规则看这个就好 http://en.wikibooks.org/wiki/Haskell/Indentation

你的 haskell.vim 已经挺不错了, record 类型很多人喜欢一行一个行首的逗号,都和 { 对齐,而结尾的 } 在你的 haskell.vim 不会和 { 对齐,而是减少了缩进

全世界我就找到你用 haskell 用 vim 有折腾能力愿意分享,python ruby 啥随意,依赖随意,毕竟是靠你提供插件……


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

| Theme: Aeros 2.0 by TheBuckmaker.com