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.
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 Int
s / 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
getArgs
: if there are no arguments the failed match will generate an uncatchable exception. Better to capture the entire output of getArgs
and test with null
before dispatching - huon 2012-04-04 15:31