Haskell: аргументы командной строки Parsing
Это скорее вопрос стиля, а не как.
Итак, у меня есть программа, для которой нужны два аргумента командной строки: строка и целое число.
Я реализовал его следующим образом:
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, поэтому я не уверен, что это ужасно неудобный и нечитаемый способ делать то, что я хочу.
Использование ленивого сопоставления шаблонов работает, но я мог видеть, как это может быть недовольным другими кодерами. И использование чтения, чтобы увидеть, если я получил успешный синтаксический анализ, определенно чувствовал себя неловко, когда писал его.
Есть ли более идиоматический способ сделать это?
Ответы
Ответ 1
Я предлагаю использовать выражение 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
Связывание с защитой, используемой здесь, это защита шаблона, новая функция, добавленная в Haskell 2010 (и обычно используемое расширение GHC до этого).
Использование reads
как это вполне приемлемо; это, по сути, единственный способ правильно восстановить неверные чтения, по крайней мере, до тех пор, пока мы не получим readMaybe
или что-то вроде этого в стандартной библиотеке (были предложения сделать это на протяжении многих лет, но они стали жертвой bikeshedding), Использование ленивого соответствия шаблонов и условных выражений для эмуляции выражения case
менее приемлемо:)
Другая возможная альтернатива, использующая расширение шаблонов просмотра, - это
case args of
[aString, reads -> [(n,_)]] ->
doStuffWith aString n
_ -> ...
Это позволяет избежать одноразовой привязки aInteger
и сохраняет логику синтаксического анализа близко к структуре списка аргументов. Однако это не стандартный Haskell (хотя расширение ни в коем случае не противоречиво).
Для более сложной обработки аргументов вы можете захотеть просмотреть специализированный модуль - System.Console.GetOpt находится в стандартной библиотеке base
, но только обрабатывает варианты (не разбор аргументов), в то время как cmdlib и cmdargs более "полные стеки" (хотя я предупреждаю вас о том, чтобы избежать "неявного" режима cmdargs, так как это грубое нечистое взломать, чтобы сделать синтаксис немного приятнее, однако "Явный" режим должен быть прекрасным, однако).
Ответ 2
Я согласен, что пакет 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
Ответ 3
В 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)
Ответ 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