Haskell:解析命令行参数

时间:2011-12-22 22:09:31

标签: haskell command-line-arguments

这更像是一个风格问题,而不是如何。

所以我有一个需要两个命令行参数的程序:一个字符串和一个整数。

我是这样实现的:

main = do
  args@(~( aString : aInteger : [] ) ) <- getArgs
  let parsed@( ~[(n,_)] ) = reads aInteger
  if length args /= 2 || L.null parsed
    then do
      name <- getProgName
      hPutStrLn stderr $ "usage: " ++ name ++ " <string> <integer>"
      exitFailure
    else do
      doStuffWith aString n

虽然这有效,但这是我第一次真正使用Haskell中的命令行参数,所以我不确定这是否是一种非常尴尬和难以理解的方式来做我想要的。

使用延迟模式匹配工作,但我可以看到它是如何被其他编码器不赞成的。使用read来看看我是否有一个成功的解析在编写时肯定感到尴尬。

是否有更惯用的方式来做到这一点?

4 个答案:

答案 0 :(得分:20)

我建议使用case表达式:

main :: IO ()
main = do
  args <- getArgs
  case args of
    [aString, aInteger] | [(n,_)] <- reads aInteger ->
      doStuffWith aString n
    _ -> do
      name <- getProgName
      hPutStrLn stderr $ "usage: " ++ name ++ " <string> <integer>"
      exitFailure

这里使用的守卫中的绑定是pattern guard,这是Haskell 2010中添加的新功能(以及之前常用的GHC扩展)。

像这样使用reads是完全可以接受的;它基本上是从无效读取中正确恢复的唯一方法,至少直到我们在标准库中得到readMaybe或类似的东西(多年来一直有提议这样做,但它们已经成为牺牲品bikeshedding)。使用延迟模式匹配和条件来模拟case表达式是不太可接受的:)

另一种使用view patterns扩展名的替代方法是

case args of
  [aString, reads -> [(n,_)]] ->
    doStuffWith aString n
  _ -> ...

这避免了一次性使用aInteger绑定,并使“解析逻辑”保持接近参数列表的结构。但是,它不是标准的Haskell(虽然扩展绝不是有争议的)。

对于更复杂的参数处理,您可能希望查看专用模块 - System.Console.GetOpt在标准base库中,但只处理选项(不是参数解析),而{{3} }和cmdlib是更多的“全栈”解决方案(虽然我提醒你要避免使用cmdargs的“隐式”模式,因为这是一个非常不纯的黑客,使语法更好;“显式”模式应该不过很好。

答案 1 :(得分:10)

我同意optparse-applicative包非常好。真棒! 让我给出一个最新的例子。

程序将字符串和整数n作为参数,返回复制n次的字符串,并且它有一个反转字符串的标志。

-- file: repstring.hs
import Options.Applicative
import Data.Monoid ((<>))

data Sample = Sample
  { string :: String
  , n :: Int
  , flip :: Bool }

replicateString :: Sample -> IO ()
replicateString (Sample string n flip) = 
    do 
      if not flip then putStrLn repstring else putStrLn $ reverse repstring
          where repstring = foldr (++) "" $ replicate n string

sample :: Parser Sample
sample = Sample
     <$> argument str 
          ( metavar "STRING"
         <> help "String to replicate" )
     <*> argument auto
          ( metavar "INTEGER"
         <> help "Number of replicates" )
     <*> switch
          ( long "flip"
         <> short 'f'
         <> help "Whether to reverse the string" )

main :: IO ()
main = execParser opts >>= replicateString
  where
    opts = info (helper <*> sample)
      ( fullDesc
     <> progDesc "Replicate a string"
     <> header "repstring - an example of the optparse-applicative package" )

编译文件后(照常使用ghc):

$ ./repstring --help
repstring - an example of the optparse-applicative package

Usage: repstring STRING INTEGER [-f|--flip]
  Replicate a string

Available options:
  -h,--help                Show this help text
  STRING                   String to replicate
  INTEGER                  Number of replicates
  -f,--flip                Whether to reverse the string

$ ./repstring "hi" 3 
hihihi
$ ./repstring "hi" 3 -f
ihihih

现在,假设您需要一个可选参数,一个要附加在字符串末尾的名称:

-- file: repstring2.hs
import Options.Applicative
import Data.Monoid ((<>))
import Data.Maybe (fromJust, isJust)

data Sample = Sample
  { string :: String
  , n :: Int
  , flip :: Bool
  , name :: Maybe String }

replicateString :: Sample -> IO ()
replicateString (Sample string n flip maybeName) = 
    do 
      if not flip then putStrLn $ repstring ++ name  else putStrLn $ reverse repstring ++ name
          where repstring = foldr (++) "" $ replicate n string
                name = if isJust maybeName then fromJust maybeName else ""

sample :: Parser Sample
sample = Sample
     <$> argument str 
          ( metavar "STRING"
         <> help "String to replicate" )
     <*> argument auto
          ( metavar "INTEGER"
         <> help "Number of replicates" )
     <*> switch
          ( long "flip"
         <> short 'f'
         <> help "Whether to reverse the string" )
     <*> ( optional $ strOption 
          ( metavar "NAME"
         <> long "append"
         <> short 'a'
         <> help "Append name" ))

编译并享受乐趣:

$ ./repstring2 "hi" 3 -f -a rampion
ihihihrampion

答案 2 :(得分:4)

Haskell中有很多参数/选项解析库比使用read / getOpt更容易生活,现代的(optparse-applicative)示例可能会引起关注:

import Options.Applicative

doStuffWith :: String -> Int -> IO ()
doStuffWith s n = mapM_ putStrLn $ replicate n s

parser = fmap (,)
         (argument str (metavar "<string>")) <*>
         (argument auto (metavar "<integer>"))

main = execParser (info parser fullDesc) >>= (uncurry doStuffWith)

答案 3 :(得分:4)

现在,我是optparse-generic的忠实粉丝,用于解析命令行参数:

  • 它允许你解析参数(不仅仅是选项)
  • 它允许您解析选项(不仅仅是参数)
  • 可以注释参数以提供有用的帮助
  • 但你不必

随着您的计划的成熟,您可能希望得到一个完整的帮助,以及一个注释良好的选项数据类型,options-generic非常擅长。但它在解析没有任何注释的列表和元组方面也非常出色,所以你可以开始运行:

例如

{-# LANGUAGE OverloadedStrings #-}
module Main where

import Options.Generic

main :: IO ()
main = do
  (n, c) <- getRecord "Example program"
  putStrLn $ replicate n c

运行方式:

$ ./OptparseGenericExample
Missing: INT CHAR

Usage: OptparseGenericExample INT CHAR
$ ./OptparseGenericExample 5 c
ccccc