2011-12-22 111 views
24

這更多的是一個樣式問題,而不是一個如何。Haskell:解析命令行參數

所以我有一個程序需要兩個命令行參數:一個字符串和一個整數。

我實現了這種方式:

main = do 
    [email protected](~(aString : aInteger : [])) <- getArgs 
    let [email protected](~[(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,所以我不知道這是否是一個可怕的尷尬和難以理解的方式去做我想做的事。

使用懶惰模式匹配的作品,但我可以看到它是如何被其他編碼者所不悅。使用閱讀來看看我是否成功解析,寫作時肯定感到尷尬。

有沒有更習慣的方式來做到這一點?

回答

17

我建議使用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,在2010年加入Haskell的一項新功能(在此之前,一個常用的GHC擴展)。

使用reads這樣是完全可以接受的;它基本上是正確地從無效讀取中恢復的唯一方式,至少在標準庫中得到readMaybe或其他類似的東西(多年來一直有建議,但它們已經成爲自行車的犧牲品)。使用懶惰模式匹配和條件來模擬case表達是不太能接受的:)

另一種可能的替代方案中,使用擴展view patterns,是

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

這就避免了一個使用aInteger結合,並保持「解析邏輯「靠近參數列表的結構。然而,這不是標準的Haskell(儘管擴展沒有爭議)。

對於更復雜的參數處理,你可能想尋找到一個專門的模塊 - System.Console.GetOpt是標準base庫,但是隻處理選項(不參數解析),而cmdlibcmdargs更「堆滿」解決方案(儘管我警告你避免使用cmdargs的「隱式」模式,因爲它使得語法更好一些,但是「顯式」模式應該沒問題)。

+3

谷歌是你的朋友!下面是關於Haskell命令行參數的一個很好的說明: http://leiffrenzel.de/papers/commandline-options-in-haskell.html – Sanjamal 2011-12-22 22:16:43

+4

@Sanjamal:雖然這並不簡化對* arguments *的解析,只是選擇。 – ehird 2011-12-22 22:18:27

+0

你也可以使用'LambdaCase'給你'getArgs >> = \ case ...'而不是'args < - getArgs; ...' – rampion 2017-05-09 15:32:41

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) 
8

我同意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 
+0

**更新:**尚未測試,但我認爲,爲了得到一個最新的例子,必須用'strOption'和'argument auto'替代'argument str' '用'選項自動'。 – 2017-01-16 18:12:36

3

這些天,我的optparse-generic的大風扇,用於解析命令行參數:

  • 它可以讓您解析參數(不只是選項)
  • 它可以讓你解析選項(而不僅僅是參數)
  • 可以註釋的參數提供了有益的幫助
  • ,但你不必

當你的程序matu res,你可能想要提供一個完整的幫助,以及一個註釋得很好的選項數據類型,其中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