3

I know I can use interact :: (String -> String) -> IO () to conveniently read from stdin and write to stdout in a simple Haskell program (see http://learnyouahaskell.com/input-and-output).

Now I would like to add command-line arguments to make my simple program "configurable".

Is there a way to do this and still use interact (so as to obtain a configurable program with minimum effort)?

2
  • I've never used interact before. Why is it a requirement? Commented Jun 12, 2017 at 21:43
  • Using interact is not a requirement, it's just what I happened to have learned so far, and being able to extend my program with minimal modifications (in this case, still using interact) would be convenient. Commented Jun 13, 2017 at 6:21

2 Answers 2

4

This can be done by "sequencing" IO computations with do and use getArgs before using interact:

import System.Environment (getArgs)
main :: IO ()
main = do
    args <- getArgs
    interact (<whatever using args>)
Sign up to request clarification or add additional context in comments.

Comments

3

In most programs, interact is quickly replaced with the alternative of using getContents and putStr. This is because interact is very limiting in that you can only call a single function, and that single function must consume all of the input. As the complexity of the program rises, you will eventually want to break up the program into smaller functions that handle a piece of the input at a time, and the do notation makes sequencing these functions much easier.

So to mimic interact but also use command-line arguments, you might initially do this:

import System.Environment

doStuff :: [String] -> String -> String
doStuff args input = undefined -- your code here

main :: IO ()
main = do
    args <- getArgs
    contents <- getContents
    putStr (doStuff args contents)

Which, by the way, is the same as:

main = doStuff <$> getArgs <*> getContents >>= putStr

But later you may wish to add things like prompts, parsers, or file I/O:

import System.Environment

data Arg = Taco | Boring

parseArg :: String -> Arg
parseArg arg = if arg == "taco" then Taco else Boring

doStuff :: Arg -> String -> String
doStuff Taco   input = "Yum, tacos! " ++ input
doStuff Boring input = "Meh, " ++ input

main :: IO ()
main = do
    [arg] <- map parseArg <$> getArgs
    putStr "Enter the file name: "
    fileName <- getLine
    fileContents <- readFile fileName
    writeFile ("output-" ++ fileName) (doStuff arg fileContents)

1 Comment

Note: getContents reads from stdin, but uses lazy IO. Sometimes tricks are needed to read the full stream (until EOF). To force a read until EOF, use a trick like length content seq` return ()` or use System.IO.Strict.getContents from stackoverflow.com/a/29061043/1236128. I am also in favor of tacos.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.