2011-12-22 42 views
24

Esta es más una pregunta de estilo que un cómo.Haskell: Análisis de los argumentos de la línea de comando

Así que tengo un programa que necesita dos argumentos de línea de comandos: una cadena y un número entero.

he implementado de esta manera:

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 

Aunque esto funciona, esta es la primera vez que realmente he utilizado argumentos de línea de comandos en Haskell, así que no estoy seguro de si se trata de una horrible y torpe forma ilegible de hacer lo que quiero.

Utilizando los patrones perezosos juego funciona, pero pude ver cómo podría ser mal visto por otros codificadores. Y el uso de lecturas para ver si obtuve un análisis exitoso definitivamente me resultó incómodo al escribirlo.

¿Hay una forma más idiomática de hacer esto?

Respuesta

17

sugiere emplear una expresión 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 

La unión de un guardia usada aquí es una pattern guard, una nueva característica añadida en Haskell 2010 (GHC y una extensión de uso común antes de eso).

El uso de reads como este es perfectamente aceptable; es básicamente la única forma de recuperar correctamente las lecturas no válidas, al menos hasta que obtengamos readMaybe o algo así en la biblioteca estándar (ha habido propuestas para hacerlo a lo largo de los años, pero han sido víctimas de bikeshedding). Uso de coincidencia de patrones perezoso y condicionales para emular una expresión case es menos aceptable :)

Otra posible alternativa, utilizando la extensión de view patterns, es

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

Esto evita el uso de una sola aInteger de unión, y mantiene el " lógica de análisis "cerca de la estructura de la lista de argumentos". Sin embargo, no es Haskell estándar (aunque la extensión de ninguna manera es controvertida).

Para un manejo argumento más complejo, es posible que desee ver en un módulo especializado - System.Console.GetOpt es en el base biblioteca estándar, pero sólo se encarga de opciones (no análisis de argumentos), mientras que cmdlib y cmdargs son más soluciones "full-stack" (aunque le advierto que evite el modo "implícito" de cmdargs, ya que es un truco impuro para hacer la sintaxis un poco más agradable, sin embargo, el modo "Explícito" debería estar bien).

+3

Google es tu amigo! Aquí está un gran reportaje sobre argumentos de línea de comandos en Haskell: http://leiffrenzel.de/papers/commandline-options-in-haskell.html – Sanjamal

+4

@Sanjamal: Eso no simplificar el análisis de los argumentos * * Sin embargo, solo opciones. – ehird

+0

también puede soltar el enlace 'args' de un uso usando' LambdaCase' que le da 'getArgs >> = \ case ...' en lugar de 'args <- getArgs; case args of ... ' – rampion

4

Hay un montón de bibliotecas argumento/opción de análisis en Haskell que hacen la vida más fácil que con read/getOpt, un ejemplo con uno moderno (optparse-applicative) podría ser de interés:

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

Me acuerdo la optparse-applicative el paquete es muy bueno. ¡Increíble! Déjeme dar un ejemplo actualizado.

El programa toma como argumentos una cadena y un entero n, devuelve la cadena replicada n veces, y tiene una bandera que invierte la cadena.

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

Una vez que se compila el archivo (con ghc como de costumbre):

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

Ahora, supongamos que desea un argumento opcional, un nombre que se añadirán al final de la cadena:

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

Compilar y divertirse:

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

** Actualización: ** No probado aún, pero creo que para obtener un ejemplo actualizado, uno tiene que reemplazar' argument str' con 'strOption' y' argument auto 'con' opción auto'. –

3

En estos días, yo soy un gran fan de optparse-generic para analizar los argumentos de línea de comandos:

  • que le permite analizar sintácticamente argumentos (no sólo las opciones)
  • que le permite analizar opciones de la (no sólo los argumentos)
  • que puede anotar los argumentos para proporcionar una ayuda útil
  • pero usted no tiene que

medida que su programa de matu res, es posible que desee obtener una ayuda completa y un tipo de datos de opciones bien anotado, en el cual options-generic es excelente. Pero también es muy bueno para analizar las listas y tuplas sin ninguna anotación en absoluto, por lo que puede comenzar a ejecutar:

Por ejemplo

{-# LANGUAGE OverloadedStrings #-} 
module Main where 

import Options.Generic 

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

ejecuta como:

$ ./OptparseGenericExample 
Missing: INT CHAR 

Usage: OptparseGenericExample INT CHAR 
$ ./OptparseGenericExample 5 c 
ccccc 
Cuestiones relacionadas