Unable to take 2 inputs simultaneously - haskell

I'm a haskell beginner. My code:
module Main (main) where
import System.IO
--Type for identifying Students.
data Student = Student {
name :: String,
regnum :: Integer
} deriving (Show, Read)
getData :: IO (String)
getData = do
putStr "\tEnter the student's name: "
name <- getLine
putStr "\tEnter the registration number: "
regstr <- getLine
let regno = (read regstr) :: Integer
return (show ( Student name regno ))
addData :: String -> IO (Bool)
addData filename = do
fileData <- getData
appendFile filename fileData
return True
printData :: String -> IO (Bool)
printData filename = do
fileData <- readFile filename
putStrLn fileData
return True
resetFile :: String -> IO (Bool)
resetFile filename = writeFile filename "" >> return True
action :: Char -> String -> IO (Bool)
action option filename =
if option == '1'
then addData filename
else if option == '2'
then printData filename
else if option == '3'
then resetFile filename
else return False
main = do
putStr "What do you want to do?\n\t1) Add a new record to a file.\n\t2) Read a record from a file.\n\t3) Reset the file.\n>> "
option <- getChar
action option "records.txt"
The output I'm getting is:
What do you want to do?
1) Add a new record to a file.
2) Read a record from a file.
3) Reset the file.
>> 1
Enter the student's name: Enter the registration number:
Hence I'm unable to provide input to for the student's name. I'm running the code on ghci. When I tried to see how it runs as an executable it gave a even weirder output.
What do you want to do?
1) Add a new record to a file.
2) Read a record from a file.
3) Reset the file.
(Notice it doesn't print ">>"). Only after I press Enter twice does it print ">>".
I can't understand whats happening here. Other improvements to my code are very welcome.
EDIT:: By using getLine instead of getChar , the program works on ghci(thanks to Daniel Fischer). But it still doesn't work when compiled. The new output is:
What do you want to do?
1) Add a new record to a file.
2) Read a record from a file.
3) Reset the file.
1 (Typed my me)
Tom (Typed my me)
234 (Typed my me)
>> Enter the student's name: Enter the registration number:
On re-running to read the file:
What do you want to do?
1) Add a new record to a file.
2) Read a record from a file.
3) Reset the file.
2 (Typed my me)
>> Student {name = "Tom", regnum = 234}
Why is ">>" and getData's putStrs being printed after taking the input?

option <- getChar
With the default buffering settings, the programme only receives the input after a newline has been entered. The getChar, however, removes only the first character entered from the input buffer, so the following getLine reads from the input buffer until it finds a newline. In your case, immediately at the beginning.
You can (at least on *nix-ish systems, buffering control used to not work properly on Windows, I don't know if it now does) solve the problem by turning off buffering for stdin,
main = do
hSetBuffering stdin NoBuffering
...
so the getChar receives the input without a newline being typed. Alternatively, instead of using getChar for the option, use
(option:_) <- getLine
so there doesn't remain anything in the input buffer to auto-satisfy the following getLine.
Also, to get the prompts printed out before the input is entered, call
hFlush stdout
to flush the output buffer, or turn off buffering for stdout.
Most simple is probably to define
prompt :: String -> IO ()
prompt msg = do
putStr msg
hFlush stdout
and replace the calls to putStr with calls to prompt.
Critique of coding style:
getData :: IO (String)
You enclose the arguments of the IO type constructor in parentheses. That's not necessary and unidiomatic. The normal way to write it is IO String. (You probably have the parentheses from seeing IO () somewhere, but those are not parentheses enclosing no type, that's the type constructor for the () type. Confusing? Yes.)
action :: Char -> String -> IO (Bool)
action option filename =
if option == '1'
then addData filename
else if option == '2'
then printData filename
else if option == '3'
then resetFile filename
else return False
That should become a case,
action option filename
= case option of
'1' -> addData filename
'2' -> printData filename
'3' -> resetFile filename
_ -> return False
Apart from that, the code looks clean.

Related

Editable default string in Haskell's terminal input

I want to be able to prompt the user for input (let's say a FilePath), but also to offer a mutable/interactive string as a default, so instead of having the user type the full path, I can prompt with:
C:\Users\John\project\test
and have them be able to backspace 4 times and enter final to yield C:\Users\John\project\final, rather than type the entire path.
However printing a default string with putStr or System.IO.hPutStr stdout does print this default to the terminal, but does not allow me to alter any of it. E.g.
import System.IO
main = do
hSetBuffering stdout NoBuffering
putStr "C:\\Users\\John\\project\\test"
l <- getLine
doSomethingWith l
I suspect Data.Text.IO's interact may be able to do what I want but I could not get it to work.
Any suggestions would be greatly appreciated.
getLine doesn’t offer any facility for line editing. For this you can use a library like haskeline instead, for example:
import System.Console.Haskeline
main :: IO ()
main = do
runInputT defaultSettings $ do
mInput <- getInputLineWithInitial "Enter path: "
("C:\\Users\\John\\project\\test", "")
case mInput of
Nothing -> do
outputStrLn "No entry."
Just input -> do
outputStrLn $ "Entry: " ++ show input
An alternative is to invoke the program with a wrapper that provides line editing, such as rlwrap. For building a more complex fullscreen text UI, there is also brick, which provides a simple text editing component in Brick.Widgets.Edit.

Convert data read from a file to a variable in Haskell

I have a problem - I am writing in a file in Haskell (I wanna write a sentence in the file and everytime I write in it I want to overwrite the content of the file so this func does the work for me completely fine)
writeFunc message = writeFile file message where
file = "abc.txt"
And then reading from the same file
readFunc = do
let file = "abc.txt"
contents <- readFile file
return contents
And then I wanna save the things I have read in a variable:
In the terminal doing this
let textFromAFile = readFunc
results into this:
*Main> let textFromAFile = readFunc
*Main> textFromAFile
"okay"
But when I use let textFromAFile = readFunc inside my code, the code wont compile
[1 of 1] Compiling Main ( tree.hs, interpreted )
tree.hs:109:29: error:
parse error (possibly incorrect indentation or mismatched brackets)
Failed, modules loaded: none.
I wanna save it in a variable so I can use it later in other functions. Why it works in the terminal but wont compile and what I can do to make it work? ReadFunc returns IO String and is there a possible way to convert it to s String so I can use it in a pure func?
readFunc has type IO String, you can use it in another IO expression with:
someIO = do
textFromAFile <- readFunc
-- use textFromFile (String) …
-- …
for example:
someIO = do
textFromAFile <- readFunc
writeFunc (textFromAFile ++ "/")
The reason it works in the GHCi terminal is that the terminal evaluates IO a objects, so while textFromAFile is an IO String, and the terminal will thus evaluate textFromAFile.

Reading line by line in Haskell

I'm trying to read from standard input line by line, and process each line with the function of the type foo :: String -> Int. Is there any way to do that provided that we don't know the number of lines we want to read OR given that the number of lines is provided on the first line?
What I've tried
A lot of things that give meaningless errors, such as "parser error".
For example
main = do {
getLine <- getContents;
let result = show (foo getLine);
putStrLn (foo result);
}
Edit
Strange, but this does not print the length of a
main = do {
a <- getContents;
putStrLn (show (length a));
}
but, this does print 5.
main = do {
a <- getContents;
putStrLn (show 5);
}
The main example of doing that will look as this:
main = do
line <- getLine
yourfunction line
main
this will take lines forever and process them with your function, in case you want it to stop sometime, just check for a command for example:
main = do
line <- getLine
let res = yourfunction line
if res == "Exit" then IO () else main
You may use the function lines to convert the String into [String]. Afterwards map your foo over this list of lines.
Regarding the edit: Your program printing the length works for me. Try either inputting a file or - if entering input interactively - terminating it correctly (via Ctrl-d).
Sidenote: curly brackets and semicolons are rarely seen usually. But this is just style.

How can I make file I/O more transactional?

I'm writing CGI scripts in Haskell. When the user hits ‘submit’, a Haskell program runs on the server, updating (i.e. reading in, processing, overwriting) a status file. Reading then overwriting sometimes causes issues with lazy IO, as we may be able to generate a large output prefix before we've finished reading the input. Worse, users sometimes bounce on the submit button and two instances of the process run concurrently, fighting over the same file!
What's a good way to implement
transactionalUpdate :: FilePath -> (String -> String) -> IO ()
where the function (‘update’) computes the new file contents from the old file contents? It is not safe to presume that ‘update’ is strict, but it may be presumed that it is total (robustness to partial update functions is a bonus). Transactions may be attempted concurrently, but no transaction should be able to update if the file has been written by anyone else since it was read. It's ok for a transaction to abort in case of competition for file access. We may assume a source of systemwide-unique temporary filenames.
My current attempt writes to a temporary file, then uses a system copy command to overwrite. That seems to deal with the lazy IO problems, but it doesn't strike me as safe from races. Is there a tried and tested formula that we could just bottle?
The most idiomatic unixy way to do this is with flock:
http://hackage.haskell.org/package/flock
http://swoolley.org/man.cgi/2/flock
Here is a rough first cut that relies on the atomicity of the underlying mkdir. It seems to fulfill the specification, but I'm not sure how robust or fast it is:
import Control.DeepSeq
import Control.Exception
import System.Directory
import System.IO
transactionalUpdate :: FilePath -> (String -> String) -> IO ()
transactionalUpdate file upd = bracket acquire release update
where
acquire = do
let lockName = file ++ ".lock"
createDirectory lockName
return lockName
release = removeDirectory
update _ = nonTransactionalUpdate file upd
nonTransactionalUpdate :: FilePath -> (String -> String) -> IO ()
nonTransactionalUpdate file upd = do
h <- openFile file ReadMode
s <- upd `fmap` hGetContents h
s `deepseq` hClose h
h <- openFile file WriteMode
hPutStr h s
hClose h
I tested this by adding the following main and throwing a threadDelay in the middle of nonTransactionalUpdate:
main = do
[n] <- getArgs
transactionalUpdate "foo.txt" ((show n ++ "\n") ++)
putStrLn $ "successfully updated " ++ show n
Then I compiled and ran a bunch of instances with this script:
#!/bin/bash
rm foo.txt
touch foo.txt
for i in {1..50}
do
./SO $i &
done
A process that printed a successful update message if and only if the corresponding number was in foo.txt; all the others printed the expected SO: foo.txt.notveryunique: createDirectory: already exists (File exists).
Update: You actually do not want to use unique names here; it must be a consistent name across the competing processes. I've updated the code accordingly.

Haskell - Passing a variable to a sub-function

I have a function main, which has a sub-function menu. In main, I load a file, ask for the user to input their name, then call menu to display a menu. After each action in the menu has been completed, I'm going to call menu again until an exit condition is met.
I'm having trouble passing the file I'm loading in main into menu - I want to perform an action on the database for each command.
This is my code so far (i've cut out bits that are irrelevant):
main :: IO ()
main = do contents <- readFile "myfile.txt"
let finalDatabase = (read contents :: [Film])
putStr "Please enter your name: "
name <- getLine
menu finalDatabase
where menu db = do putStrLn "Please select an option:"
putStrLn "1: Display all films"
putStr "Choice: "
choice <- getLine
case choice of {
"1" -> displayAll db;
}
menu
The displayAll function just prints out a Database nicely.
I'm getting the following error in WinHugs:
ERROR file:.\films.hs:152 - Type error in function binding
*** Term : menu
*** Type : [([Char],[[Char]],Int,[[Char]])] -> IO a
*** Does not match : IO a
I thought that if I didn't specify a line like menu :: Database -> IO (), it would accept any parameters without being concerned with their type.
Any suggestions?
Edit:
Stupid mistake, I just forgot to pass the database when calling menu again after the case statement!
I thought that if I didn't specify a line like menu :: Database -> IO (), it would accept any parameters without being concerned with their type.
That's not true, in your case menu really has the type Database -> IO () so in the last line before the where, you need to give it a Database to get an IO () - probably db (as in menu db) but maybe later, you'll start modifying the database and then you can pass the new one in. Haskell doesn't just look around at what's in scope to find a suitable value of type Database!
If the database truly is fixed you don't need to pass it in and you could use something like:
main = do contents <- readFile "myfile.txt"
let finalDatabase = (read contents :: [Film])
putStr "Please enter your name: "
name <- getLine
menu
where menu = do putStrLn "Please select an option:"
putStrLn "1: Display all films"
putStr "Choice: "
choice <- getLine
case choice of {
"1" -> displayAll finalDatabase -- instead of db
}
menu
But, as said, that's going to break when you start modifying the 'database'. Let's say you define a function doSomethingWithDatabaseDependingOnChoice :: String -> [Film] -> IO [Film] which takes the choice and the old database, performs some in/output and returns a new database, you could use it as follows:
<same as before>
menu finalDatabase
where menu db = do putStrLn "Please select an option:"
putStrLn "1: Display all films"
putStr "Choice: "
choice <- getLine
newDb <- doSomethingWithDatabaseDependingOnChoice choice db
menu newDb
Another option would be to use implicit parameters, but I'm guessing that's a bit too advanced at the moment!
First of all, no, not specifying a type, does not mean that the function will accept any type. It means that the type will be inferred.
The reason that you're getting that particular type error is that at the end of menu's do block, you write menu, which is a function and not a value of type IO. I'm not exactly sure why you do that. If you want to create an infinite loop, you should change the last call to menu db, instead of returning the function unapplied. If you don't want an infinite loop, I don't see why you use menu at the end at all.

Resources