Home > front end >  How to correctly parse arguments with Haskell?
How to correctly parse arguments with Haskell?

Time:01-15

I'm trying to learn how to work with IO in Haskell by writing a function that, if there is a flag, will take a list of points from a file, and if there is no flag, it asks the user to enter them.

dispatch :: [String] -> IO ()
dispatch argList  = do
  if "file" `elem` argList
    then do
       let (path : otherArgs) = argList
       points <- getPointsFile path

    else
      print "Enter a point in the format: x;y"
      input <- getLine
      if (input == "exit")
        then do
          print "The user inputted list:"
          print $ reverse xs
        else (inputStrings (input:xs))

    if "help" `elem` argList
      then help
      else return ()
dispatch [] = return ()
dispatch _ = error "Error: invalid args"

getPointsFile :: String -> IO ([(Double, Double)])
getPointsFile path = do
  handle <- openFile path ReadMode
  contents <- hGetContents handle
  let points_str = lines contents
  let points = foldl (\l d -> l    [tuplify2 $ splitOn ";" d]) [] points_str
  hClose handle
  return points

I get this: do-notation in pattern Possibly caused by a missing 'do'?` after `if "file" `elem` argList.

I'm also worried about the binding issue, assuming that I have another flag that says which method will be used to process the points. Obviously it waits for points, but I don't know how to make points visible not only in if then else, constructs. In imperative languages I would write something like:

init points
if ... { points = a}
else points = b
some actions with points

How I can do something similar in Haskell?

CodePudding user response:

Here's a fairly minimal example that I've done half a dozen times when I'm writing something quick and dirty, don't have a complicated argument structure, and so can't be bothered to do a proper job of setting up one of the usual command-line parsing libraries. It doesn't explain what went wrong with your approach -- there's an existing good answer there -- it's just an attempt to show what this kind of thing looks like when done idiomatically.

import System.Environment
import System.Exit
import System.IO

main :: IO ()
main = do
    args <- getArgs
    pts <- case args of
        ["--help"] -> usage stdout ExitSuccess
        ["--file", f] -> getPointsFile f
        [] -> getPointsNoFile
        _ -> usage stderr (ExitFailure 1)
    print (frobnicate pts)

usage :: Handle -> ExitCode -> IO a
usage h c = do
    nm <- getProgName
    hPutStrLn h $ "Usage: "    nm    " [--file FILE]"
    hPutStrLn h $ "Frobnicate the points in FILE, or from stdin if no file is supplied."
    exitWith c

getPointsFile :: FilePath -> IO [(Double, Double)]
getPointsFile = {- ... -}

getPointsNoFile :: IO [(Double, Double)]
getPointsNoFile = {- ... -}

frobnicate :: [(Double, Double)] -> Double
frobnicate = {- ... -}

CodePudding user response:

if in Haskell doesn't inherently have anything to do with control flow, it just switches between expressions. Which, in Haskell, happen to include do blocks of statements (if we want to call them that), but you still always need to make that explicit, i.e. you need to say both then do and else do if there are multiple statements in each branch.

Also, all the statements in a do block need to be indented to the same level. So in your case

  if "file" `elem` argList
       ...
  if "help" `elem` argList

Or alternatively, if the help check should only happen in the else branch, it needs to be indented to the statements in that do block.

Independent of all that, I would recommend to avoid parsing anything in an IO context. It is usually much less hassle and easier testable to first parse the strings into a pure data structure, which can then easily be processed by the part of the code that does IO. There are libraries like cmdargs and optparse-applicative that help with the parsing part.

  • Related