Haskell:解析命令行参数

ram*_*ion 27 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
Run Code Online (Sandbox Code Playgroud)

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

使用惰性模式匹配工作,但我可以看到它是如何被其他编码器所厌恶.并且使用read来查看我是否有一个成功的解析在写它时肯定感到尴尬.

有没有比较惯用的方法呢?

ehi*_*ird 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
Run Code Online (Sandbox Code Playgroud)

这里使用的防护装置中的绑定是模式防护,是Haskell 2010中添加的新功能(以及之前常用的GHC扩展).

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

使用视图模式扩展的另一种可能的替代方案是

case args of
  [aString, reads -> [(n,_)]] ->
    doStuffWith aString n
  _ -> ...
Run Code Online (Sandbox Code Playgroud)

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

对于更复杂的参数处理,您可能需要查看专用模块 - System.Console.GetOpt在标准base库中,但只处理选项(不是参数解析),而cmdlibcmdargs是更"全栈"解决方案(虽然我提醒你要避免cmdargs的"隐式"模式,因为这是一个非常不纯的黑客,使语法更好一些;但"显式"模式应该没问题.

  • @Sanjamal:这并不简化*arguments*的解析,只是选项. (4认同)
  • 谷歌是你的朋友!这是关于Haskell命令行args的一篇很好的文章:http://leiffrenzel.de/papers/commandline-options-in-haskell.html (3认同)

Sté*_*ent 11

我同意这个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" )
Run Code Online (Sandbox Code Playgroud)

编译文件后(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
Run Code Online (Sandbox Code Playgroud)

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

-- 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" ))
Run Code Online (Sandbox Code Playgroud)

编译并享受乐趣:

$ ./repstring2 "hi" 3 -f -a rampion
ihihihrampion
Run Code Online (Sandbox Code Playgroud)


ber*_*rio 6

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)
Run Code Online (Sandbox Code Playgroud)


ram*_*ion 5

这些天,我非常喜欢使用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
Run Code Online (Sandbox Code Playgroud)

运行如下:

$ ./OptparseGenericExample
Missing: INT CHAR

Usage: OptparseGenericExample INT CHAR
$ ./OptparseGenericExample 5 c
ccccc
Run Code Online (Sandbox Code Playgroud)