这更像是一个风格问题,而不是如何。
所以我有一个需要两个命令行参数的程序:一个字符串和一个整数。
我是这样实现的:
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来看看我是否有一个成功的解析在编写时肯定感到尴尬。
是否有更惯用的方式来做到这一点?
答案 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