Dispatching to correct function with command line

2019-03-02 21:11发布

问题:

I'm writing a little command-line program in Haskell. I need it to dispatch to the correct encryption function based on the command line arguments. I've gotten that far, but then I need the remaining arguments to get passed to the function as parameters. I've read:

http://learnyouahaskell.com/input-and-output

That's gotten me this far:

import qualified CaesarCiphers
import qualified ExptCiphers

dispatch::[(String, String->IO ())]
dispatch = [("EEncipher", ExptCiphers.exptEncipherString)
            ("EDecipher", ExptCiphers.exptDecipherString)
            ("CEncipher", CaesarCiphers.caesarEncipherString)
            ("CDecipher", CaesarCiphers.caesarDecipherString)
            ("CBruteForce", CaesarCiphers.bruteForceCaesar)]

main = do
    (command:args) <- getArgs

Each of the functions takes some arguments that I won't know untill run-time. How do I pass those into a function seeing as they'll be bound up in a list? Do I just grab them manually? Like:

exampleFunction (args !! 1) (args !! 2) 

That seems kind of ugly. Is there some sort of idiomatic way to do this? And what about error checking? My functions aren't equipped to gracefully handle errors like getting passed parameters in an idiotic order.

Also, and importantly, each function in dispatch takes a different number of arguments, so I can't do this statically anyways (as above.) It's too bad unCurry command args isn't valid Haskell.

回答1:

One way is to wrap your functions inside functions that do further command line processing. e.g.

dispatch::[(String, [String]->IO ())]
dispatch = [("EEncipher", takesSingleArg ExptCiphers.exptEncipherString)
            ("EDecipher", takesSingleArg ExptCiphers.exptDecipherString)
            ("CEncipher", takesTwoArgs CaesarCiphers.caesarEncipherString)
            ("CDecipher", takesTwoArgs CaesarCiphers.caesarDecipherString)
            ("CBruteForce", takesSingleArg CaesarCiphers.bruteForceCaesar)]

-- a couple of wrapper functions:

takesSingleArg :: (String -> IO ()) -> [String] -> IO ()
takesSingleArg act [arg] = act arg
takesSingleArg _   _     = showUsageMessage

takesTwoArgs :: (String -> String -> IO ()) -> [String] -> IO ()
takesTwoArgs act [arg1, arg2] = act arg1 arg2
takesTwoArgs _   _            = showUsageMessage

-- put it all together

main = do
    (command:args) <- getArgs
    case lookup command dispatch of
         Just act -> act args
         Nothing  -> showUsageMessage

You can extend this by having variants of the wrapper functions perform error checking, convert (some of) their arguments into Ints / custom datatypes / etc as necessary.

As dbaupp notes, the way we pattern match on getArgs above isn't safe. A better way is

run :: [String] -> IO ()
run [] = showUsageMessage
run (command : args)
   = case lookup command dispatch of
          Just act -> act args
          Nothing  -> showUsageMessage

main = run =<< getArgs