Using getArgs or user input - haskell

New to Haskell and wrestling with a problem. I know why my code doesn't work, but I cannot figure out the solution.
The intention is for the user to provide a file name either via argument or, if none provided, prompt the user to enter the data. A message with the file name is printed to the screen and the file is processed.
import System.Environment
main = do
args <- getArgs
let file = if null args
then do putStr "File: " ; getLine
else head args
putStr "processing"
putStrLn file
writeFile file "some very nice text"
The code is wrong, of course, but demonstrates the logic I've been wrestling with. Neither Happy Learn Haskell nor Learn you Haskell could put me over the hump. The closest thread I could find was this one. Many thanks for assistance.

Let us analyze the type of file:
let file = if null args
then do putStr "File: " ; getLine
else head args
Here the if-then-else statement has as condition null args, which is indeed a Bool, so we are safe. But the the then part has as type IO String. So Haskell reasons that args should be an [IO String], but it is not: it is a list of Strings, so [String].
Later another problem pops up: you use file as a String, but it is not: it is still an IO String.
There are a few ways to fix it. Probably the minimal change is to use pure to wrap the head args back into an IO, and use replace the let clause with a <- statement:
import System.Environment
main = do
args <- getArgs
file <- if null args
then do putStr "File: " ; getLine
else pure (head args)
putStr "processing "
putStrLn file
writeFile file "some very nice text"

Related

Why am i getting a parse error and how can i solve this error in haskell?

I'm new to Haskell and while reading real-world Haskell I came across a problem:
Q) Using the command framework from the earlier section “A Simple Command-Line Framework” on page 71, write a program that prints the first word of each line of its input.
The command framework is:
-- file: ch04/InteractWith.hs
-- Save this in a source file, e.g., Interact.hs
import System.Environment (getArgs)
interactWith function inputFile outputFile = do
input <- readFile inputFile
writeFile outputFile (function input)
main = mainWith myFunction
where mainWith function = do
args <- getArgs
case args of
[input,output] -> interactWith function input output
_ -> putStrLn "error: exactly two arguments needed"
-- replace "id" with the name of our function below
myFunction = id
My solution to the problem is:
-- file: ch04/InteractWith.hs
-- Save this in a source file, e.g., Interact.hs
import System.Environment (getArgs)
interactWith function inputFile outputFile = do
input <- readFile inputFile
writeFile outputFile (function input)
main = mainWith myFunction
where mainWith function = do
args <- getArgs
case args of
[input,output] -> interactWith function input output
_ -> putStrLn "error: exactly two arguments needed"
-- replace "id" with the name of our function below
myFunction = concat (map (++"\n") (map head (map words (lines input))))
As per instructions, whenever I try to compile this program using ghc --make filename I get a parse error i.e.
error: parse error on input ‘args’
|
36 | args <- getArgs
| ^^^^
Your indentation is off.
Haskell is sensitive to whitespace. In particular, among other things, the "inside" of a "block" must generally be indented farther to the right than the beginning of that block. In your case this means that the inside of do must be indented to the right of mainWith on the preceding line.
Try this:
main = mainWith myFunction
where mainWith function = do
args <- getArgs
case args of
[input,output] -> interactWith function input output
_ -> putStrLn "error: exactly two arguments needed"

Read a single int from a file and print it

How do I create a program that reads a line from a file, parse it to an int and print it(ignoring exceptions of course). Is there anything like "read" but for IO String?
I've got this so far but I couldn't get around the IO types:
readFromFile = do
inputFile <- openFile "catalogue.txt" ReadMode
isbn <- read( hGetLine inputFile)
hClose inputFile
You can specify the type explicitly, change the read line to
isbn <- fmap read (hGetLine inputFile) :: IO Int
As hGetLine inputFile is of type IO String, you should use fmap to get "inside" to read as an Int.
You can use the readFile function to convert your file to a string.
main = do
contents <- readFile "theFile"
let value = read $ head $ lines contents::Int
print value
You should add better error detection, or this program will fail if there isn't a first line, or if the value is malformed, but this is the basic flow....
First, observe that reading stuff and then immediately printing it can result in mysterious errors:
GHCi, version 8.0.0.20160421: http://www.haskell.org/ghc/ :? for help
Prelude λ read "123"
*** Exception: Prelude.read: no parse
The reason is that you don't specify what type you want to read. You can counter this by using type annotations:
Prelude λ read "123" :: Integer
123
but it is sometimes easier to introduce a little helper function:
Prelude λ let readInteger = read :: String -> Integer
Prelude λ readInteger "123"
123
Now to the main problem. read( hGetLine inputFile) doesn't work because hGetLine inputFile returns and IO String and read needs a String. This can be solved in two steps:
line <- hGetLine inputFile
let isbn = readInteger line
Note two different constructs <- and let .. =, they do different things. Can you figure out exactly what?
As shown in another answer, you can do it in a less verbose manner:
isbn <- fmap readInteger (hGetLine inputFile)
which is great if you do a simple thing like read. But it is often desirable to explicitly name intermediate results. You can use <- and let .. = constructs in such cases.

Read in multiple lines from standard input with arguments in Haskell

I'm trying to read in multiple lines from standard input in Haskell, plus one argument, then do something with the current line and write something to the standard output.
In my case I am trying to normalize lambda expressions. The program may receive 1 or more lambda expressions to normalize and then it has to write the result (normalized form or error) to the standard output. And the program may receive an argument (the max number of reductions). Here is the main function:
main :: IO ()
main = do
params <- getArgs
fullLambda <- getLine
let lambda = convertInput fullLambda
let redNum | (length params) == 1 = read (head params)
| otherwise = 100
case (parsing lambda) of
Left errorExp -> putStrLn ("ERROR: " ++ lambda)
Right lambdaExp -> do
let normalizedLambdaExp = reduction lambdaExp redNum
if (isNormalForm normalizedLambdaExp) && (isClosed lambdaExp)
then putStrLn ("OK: " ++ show normalizedLambdaExp)
else putStrLn ("ERROR: " ++ lambda)
where
convertInput :: String -> String
convertInput ('\"':xs) = take ((length xs) - 2) xs
convertInput input = input
So this code handles one line and completes the reductions and then writes something to the standard output. How can I change this to handle multiple lines? I've read about replicateM but I can't seem to grasp it. My mind is very OO so I was thinking maybe some looping somehow, but that is surely not the preferred way.
Also, this program has to be able to run like this:
echo "(\x.x) (\x.x)" | Main 25
And will produce:
OK: (\x.x)
And if there are multiple lines, it has to produce the same kind of output for each line, in new lines.
But also has to work without the argument, and has to handle multiple lines. I spent time on google and here, but I'm not sure how the argument reading will happen. I need to read in the argument once and the line(s) once or many times. Does someone know a not too lengthy solution to this problem?
I've tried it like this, too (imperatively):
main :: IO ()
main = do
params <- getArgs
mainHelper params
main
mainHelper :: [String] -> IO ()
mainHelper params = do
fullLambda <- getLine
And so on, but then it puts this to the standard output as well:
Main: <stdin>: hGetLine: end of file
Thank you in advance!
It appears you want to:
Parse a command line option which may or may not exist.
For each line of input process it with some function.
Here is an approach using lazy IO:
import System.Environment
import Control.Monad
main = do args <- getArgs
let option = case args of
[] -> ... the default value...
(a:_) -> read a
contents <- getContents
forM_ (lines contents) $ \aline -> do
process option aline
I am assuming your processing function has type process :: Int -> String -> IO (). For instance, it could look like:
process :: Int -> String -> IO ()
process option str = do
if length str < option
then putStrLn $ "OK: " ++ str
else putStrLn $ "NOT OK: line too long"
Here's how it works:
contents <- getContents reads all of standard input into the variable contents
lines contents breaks up the input into lines
forM_ ... iterates over each line, passing the line to the process function
The trick is that getContents reads standard input lazily so that you'll get some output after each line is read.
You should be aware that there are issues with lazy IO which you may run into when your program becomes more complex. However, for this simple use case lazy IO is perfectly fine and works well.

parsec error in haskelwiki tutorial

I was following the code in http://www.haskell.org/haskellwiki/Hitchhikers_guide_to_Haskell, and the code (in chapter 2) gives an error. There is no author name/email mentioned with the tutorial, so I am coming here for advise. The code is below, and the error occurs on the "eof" word.
module Main where
import Text.ParserCombinators.Parsec
parseInput =
do dirs <- many dirAndSize
eof
return dirs
data Dir = Dir Int String deriving Show
dirAndSize =
do size <- many1 digit
spaces
dir_name <- anyChar `manyTill` newline
return (Dir (read size) dir_name)
main = do
input <- getContents
putStrLn ("Debug: got inputs: " ++ input)
That tutorial was written a long time ago, when parsec was simple. Nowadays, since parsec-3, the library can wrap monads, so you now have to specify (or otherwise disambiguate) the type to use at some points. This is one of them, giving eof e.g. the expression type signature eof :: Parser () makes it compile.

Haskell IO sharing parameters

In Haskell, is it possible to share user input from one IO function to the other?
For instance, if I had:
main = do
putStrLn "Give me a number!"
my_stuff <- getLine
let nump = read (my_stuff)::Int
another_function nump
Where another_function is also an IO function with a do construct.
another_function nump = do
putStrLn nump
putStrLn "Try again!"
main
This would make sense in the fantasy-world Haskell interpreter I have in my head. However, in the real world: my_stuff is unbound in another_function; and in main, my_stuff requires to be of type IO t but it isn't.
The above code would (most probably) be very offensive to Haskellers, yet I hope that it conveyed what exactly I'm aiming for...
How do I work around this?
This code works. Is this what you want to do? If not, can you provide with the code that doesn't work?
main = do
putStrLn "Give me a number!"
my_stuff <- getLine
let nump = read (my_stuff)::Int
another_function nump
another_function nump = do
putStrLn $ show nump
putStrLn "Try again!"
main

Resources