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)
     
    
interactbefore. Why is it a requirement?interactis 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 usinginteract) would be convenient.