2011-12-22 77 views
24

Câu hỏi kiểu này nhiều hơn là cách thực hiện.Haskell: Phân tích cú pháp đối số dòng lệnh

Vì vậy, tôi đã có một chương trình cần hai đối số dòng lệnh: một chuỗi và một số nguyên.

tôi thực hiện nó theo cách này:

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 

Trong khi làm việc này, đây là lần đầu tiên tôi đã thực sự sử dụng args dòng lệnh trong Haskell, vì vậy tôi không chắc chắn cho dù đây là một khủng khiếp vụng về và cách không thể đọc được để làm những gì tôi muốn.

Sử dụng các công cụ so khớp mẫu phù hợp, nhưng tôi có thể thấy cách các trình lập trình khác cau mày. Và việc sử dụng số lần đọc để xem liệu tôi có phân tích cú pháp thành công chắc chắn cảm thấy khó xử khi viết nó hay không.

Có cách nào thành ngữ hơn để thực hiện việc này không?

Trả lời

17

tôi đề nghị sử dụng một biểu thức 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 

Các ràng buộc trong một người bảo vệ sử dụng ở đây là một pattern guard, một tính năng mới được thêm vào trong Haskell 2010 (và một phần mở rộng GHC thường được sử dụng trước đó).

Sử dụng reads như thế này hoàn toàn có thể chấp nhận được; Về cơ bản, đó là cách duy nhất để phục hồi đúng cách từ các lần đọc không hợp lệ, ít nhất là cho đến khi chúng tôi nhận được readMaybe hoặc một thứ gì đó của nó trong thư viện chuẩn (đã có đề xuất để làm điều đó qua nhiều năm, nhưng họ đã làm hỏng con mồi để đạp xe). Sử dụng mô hình kết hợp lười biếng và điều kiện để mô phỏng một biểu case ít chấp nhận được :)

Một thay thế có thể sử dụng phần mở rộng view patterns, là

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

Điều này tránh sự chỉ sử dụng một aInteger ràng buộc, và giữ cho " phân tích logic "gần với cấu trúc của danh sách đối số. Tuy nhiên, nó không phải là tiêu chuẩn Haskell (mặc dù phần mở rộng là do không có nghĩa là gây tranh cãi).

Đối với việc xử lý tranh cãi phức tạp hơn, bạn có thể muốn xem xét một mô-đun chuyên ngành - System.Console.GetOpt là trong base thư viện chuẩn, nhưng chỉ xử lý tùy chọn (không lập luận phân tích), trong khi cmdlibcmdargs có nhiều "full-stack" giải pháp (Mặc dù tôi cảnh báo bạn tránh chế độ "Ngụ ý" của cmdarg, vì nó là một hack không thuần khiết để làm cho cú pháp đẹp hơn một chút, tuy nhiên chế độ "Rõ ràng" sẽ tốt, tuy nhiên).

+3

Google là bạn của bạn! Đây là cách viết tuyệt vời trên dòng lệnh args trong Haskell: http://leiffrenzel.de/papers/commandline-options-in-haskell.html – Sanjamal

+4

@Sanjamal: Điều đó không đơn giản hóa việc phân tích cú pháp của * đối số *, tuy nhiên, chỉ là lựa chọn. – ehird

+0

bạn cũng có thể thả liên kết 'args' sử dụng một lần bằng cách sử dụng' LambdaCase' cho bạn 'getArgs >> = \ case ...' thay vì 'args <- getArgs; case args of ... ' – rampion

4

Có rất nhiều thư viện luận/tùy chọn phân tích cú pháp trong Haskell làm cho cuộc sống dễ dàng hơn với read/getOpt, một ví dụ với một hiện đại (optparse-applicative) có thể quan tâm:

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

Tôi đồng ý các optparse-applicative gói là rất tốt đẹp. Tuyệt vời! Hãy để tôi đưa ra một ví dụ cập nhật.

Chương trình lấy làm đối số một chuỗi và số nguyên n, trả về chuỗi nhân bản n lần và có một cờ đảo ngược chuỗi.

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

Một khi tập tin được biên dịch (với ghc như bình thường):

$ ./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 

Bây giờ, giả sử bạn muốn một đối số tùy chọn, một tên để nối thêm vào cuối chuỗi:

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

Biên dịch và vui chơi:

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

** Cập nhật: ** Chưa được thử nghiệm, nhưng tôi nghĩ rằng để có được một ví dụ cập nhật, người ta phải thay thế' đối số str' bằng 'strOption' và' argument auto 'with' option auto'. –

3

Những ngày này, tôi là một fan hâm mộ lớn của optparse-generic cho phân tích đối số dòng lệnh:

  • nó cho phép bạn phân tích lập luận (không chỉ là tùy chọn)
  • nó cho phép bạn phân tích tùy chọn (không chỉ đối số)
  • bạn thể chú thích các lý lẽ để cung cấp một trợ giúp hữu ích
  • nhưng bạn không cần phải

Như matu chương trình của bạn res, bạn có thể muốn tìm kiếm trợ giúp hoàn chỉnh và loại dữ liệu tùy chọn được chú thích rõ ràng, trong đó options-generic thật tuyệt vời. Nhưng nó cũng tuyệt vời tại phân tích danh sách và các bộ mà không cần bất kỳ chú thích nào cả, vì vậy bạn có thể rơi xuống đất chạy:

Ví dụ

{-# LANGUAGE OverloadedStrings #-} 
module Main where 

import Options.Generic 

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

Chạy như:

$ ./OptparseGenericExample 
Missing: INT CHAR 

Usage: OptparseGenericExample INT CHAR 
$ ./OptparseGenericExample 5 c 
ccccc 
Các vấn đề liên quan