1

I want to write a program in Haskell which will take command line arguments. For example: to print the sum of the first 6 elements of the series (which will be calculated by another function), I will write:

sum 6

and the correct answer should be displayed. I have to do this for another 5-7 different commands by checking the command line. How should I do it? Is switch case a good idea? If so, can anyone tell me how it can be done.

SOLUTION:

main = do
--Get some input
    f <- getLine
--Split the input into 2 strings; one is COMMAND field and other is the ARGUMENT field using the condition the there is one space between them   
    let cmd = takeWhile (/=' ') f
    let arg = dropWhile (/=' ') f


    let val = tail arg  
    let p = read val::Int


--Check for the COMMAND     
    case cmd of
        "SUM" -> if (empty arg) then do { putStrLn "ERR"; exitWith ExitSuccess} else if (check val) then print (sum1 p) else do { putStrLn "ERR"; exitWith ExitSuccess}
        "NTH" -> if (empty arg) then do { putStrLn "ERR"; exitWith ExitSuccess} else if (check val) then print (fact p) else do { putStrLn "ERR"; exitWith ExitSuccess}
        "BOUNDS" -> if (empty arg) then do { putStrLn "ERR"; exitWith ExitSuccess} else if (check val == False) then do { putStrLn "ERR"; exitWith ExitSuccess} else if (p > 1) then do { print c; print d} else do { putStrLn"ERR"; exitWith ExitSuccess}  
        "QUIT" -> if (empty arg) then exitWith ExitSuccess else do { putStrLn "ERR"; exitWith ExitSuccess}
            _ -> do { putStrLn "ERR"; exitWith ExitSuccess}

--Repeat main until QUIT
    main
3
  • 2
    Have you looked at GetOpt? Commented Nov 15, 2014 at 8:50
  • Is this homework? Do you have any code so far? I think that you should first write some basic code that will enable you to do this. Perhaps start with let args = ["sum","6"] to make sure that you actually know how to do this. Later, have a look at getArgs from System.Environment and work that way into your working code. Commented Nov 15, 2014 at 9:52
  • When you say "take command line arguments", are you writing your own little command shell, or are these 5-7 commands all individual programs to be run from the bash command line? If the former than you could just write your own little command parser with Parsec. If the latter then the other answers here are probably better. Commented Nov 15, 2014 at 14:50

3 Answers 3

6

optparse-applicative is one example of a library which supports this kind of sub-command parsing.

Let's say your program has two commands for now, "sum" and "mean". We can represent the command and its arguments using an algebraic data type, here called Command.

import Data.Monoid (mconcat)
import Options.Applicative

data Command = Sum Integer
             | Mean Integer
               -- et cetera

We can build a parser which accepts all of the commands, by writing parsers for each individual command, and composing them.

parseNumber :: Parser Integer
parseNumber = argument auto (metavar "N")

sumParser :: ParserInfo Command
sumParser = info (Sum <$> parseNumber)
                 (progDesc "Sum first N elements in series")

meanParser :: ParserInfo Command
meanParser = info (Mean <$> parseNumber)
                  (progDesc "Mean of first N elements in series")

commandParser :: ParserInfo Command
commandParser = info commands $ progDesc "My program" where
    commands = subparser $ mconcat [
          command "sum" sumParser
        , command "mean" meanParser
        ]

If you are wondering what Parser and ParserInfo are about: Usually we build a Parser, then put it into a ParserInfo, using the info combinator to decorate it with additional information about how it should be run (for example, with progDesc). Parsers may be composed with other Parsers, typically using the applicative combinators, but a ParserInfo is only a Functor, as it represents the entry point to the program.

Since each command is like a little sub-program, we need a ParserInfo for each one. The command and subparser combinators let us take some ParserInfos and wrap them up in a Parser, turning multiple entry points into one.

Once we have a result from the parser, we can dispatch to the appropriate routine by pattern matching on the result.

main :: IO ()
main = do
  cmd <- execParser commandParser
  case cmd of
    Sum n  -> return () -- TODO perform sum command
    Mean n -> return () -- TODO perform mean command
Sign up to request clarification or add additional context in comments.

5 Comments

It seems to me like the question is either homework or the asker is a beginner. Giving out a complete solution is kind of missing the point isn't it?
I'm not just answering for the OP, but for other people who might search for this question, too.
optparse-applicative is the most powerful command line parser out there, but may be a bit difficult for beginners to get their heads around. Anyone looking for simple command line parsing for a throw-away utility should take a look at getopt.
@PaulJohnson Maybe you'd like to add an illustrative example as an answer?
Actually I am a beginner.
4

Of course, if you have the time and the need, is much better to use a command line parser library than a switch case. A proper parser gives you the ability to have flags in any order, automatic documenation etc ... Although if you don't need any of this now, you might need it later.

However, pattern matching allows you check value(s) within a list, but although the size of the list, and this at the same time. This makes writing poor man command line parsing dead-easy in Haskell.

Example

main = do
     args <- getArg
     case args of
        ["command1", a, b] -> command1 a b -- 2 argument
        ["command2", a ]   -> command2 a -- 1 argument
        "command3":as      -> command3 as  -- n arguments
        otherwise          -> putStrLn "Please read the code to see which arguments are acceptable :-)"

So even though I would propably recommend using a parsing library, if you only need a couple of options without flags , and don't have time to learn/choose one, a simple case ... of is pretty neat and much quicker/simpler to write.

Comments

0

You can write your own simple applicative-style parser in just a few lines. The idea is: accept a list of string pairs, where the first string is an option name and the second string is an option value, lookup for a current option name, and if it's found, treat the associated value somehow and delete the pair from the list. If it's not found, return Nothing. So Parser is defined like this:

type Parser = StateT [(String, String)] Maybe

And here is the main function:

option :: (String -> Maybe a) -> String -> Parser a
option f str = StateT $ \xs -> do
        (v, xs') <- lookupDelete str xs
        v' <- f v
        return (v', xs')

where lookupDelete does what it says. Actual option parsers are:

sopt :: String -> Parser String
sopt = option Just

opt :: Read a => String -> Parser a
opt = option $ reads >>> listToMaybe >=> finish

finish (x, []) = Just x
finish  _      = Nothing

The opt parser tries to read a string, and it succeeds if the string is read fully.

optPairs  []                     = Just []
optPairs (('-':'-':name):opt:xs) = ((name, opt) :) <$> optPairs xs
optPairs  _                      = Nothing

This function splits input into pairs. And lastly

parse :: Parser a -> String -> Maybe a
parse p = words >>> optPairs >=> runStateT p >=> finish

Here is an example:

data SubCommand = SubCommand String (Double, Double)
                deriving (Show)

data Command = Sum [Integer]
             | Sub SubCommand
             deriving (Show)

subcommandParser :: Parser SubCommand
subcommandParser = SubCommand <$> sopt "str" <*> opt "dbls"

commandParser :: Parser Command
commandParser = Sum <$> opt "sum" <|> Sub <$> subcommandParser

main = mapM_ (print . parse commandParser)
    [ "--sum [1,2,3,4]"
    , "--str option --dbls (2.2,0)"
    , "--dbls (2.2,0) --str option"
    , "--smth smth"
    ]

Results in

Just (Sum [1,2,3,4])
Just (Sub (SubCommand "option" (2.2,0.0)))
Just (Sub (SubCommand "option" (2.2,0.0)))
Nothing

The whole code: http://lpaste.net/114365

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.