Dispatching to correct function with command line arguments in Haskell

Go To StackoverFlow.com

1

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.

2012-04-04 05:37
by Josh Infiesto
CmdLib has an interesting solution to this problem. Not to discourage you from finding your own solution, though - ephemient 2012-04-04 06:42


7

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
2012-04-04 08:13
by dave4420
One should probably avoid the pattern match on 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
In your wrapper functions, the order of the arguments seems to be wrong. The signature is correct, but in the definitions you are expecting the string list first - Frerich Raabe 2012-04-04 20:08
Thanks @FrerichRaabe --- fixed - dave4420 2012-04-04 20:34
@dave4420: Your answer got me wondering whether this can be simplified further. I decided to create a dedicated question for this - Frerich Raabe 2012-04-05 07:37
Ads