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.
Related
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.
I'm trying to process arrow key ANSI escape sequences i.e.
up - "\033[A"
down - "\033[B"
left - "\033[D"
right - "\033[C"
in my programme so when I press the up/down/left/right arrow key, it won't have to look like this:
% stack runghc test.hs
Input a name?
^[[A^[[B^[[C^[[D^
on my stdin, but rather I would like those keys to be suppressed or even better,
for them to actually work(i.e move the cursor left/right). My code is as follows:
main = do putStrLn "Input a name?"
name <- getLine
putStrLn $ ("His name is " ++ name)
Any help would be appreciated. Thanks in advance.
The easiest way to get readline-like functionality is to just use readline. For most simple use cases, rlwrap is good enough, as described in One REPL to bind them all?. If you need to do fancier integrations, you can use the readline package.
I had trouble installing the readline library due to some errors and decided to use the haskeline library, which is a more portable pure-haskell replacement for it.
Using its syntax and modifying the earlier code, I got:
main :: IO ()
main = do putStrLn "Input a name?"
runInputT defaultSettings insertion
where
insertion :: InputT IO ()
insertion = do
minput <- getInputLine ""
case minput of
Nothing -> return ()
Just input -> do outputStrLn $ "His name is " ++ input
Which solves the problem as I am now able to move my cursor with the arrow keys freely without having to see any trailing ANSI escape sequences as shown below:
I'm getting an error with the following code:
main = do
putStrLn "Enter parameter"
parameter <- getLine
if head parameter == "c" then
let object = getObject parameter
print object
else
putStrLn "Error, try again"
main
The error I get is:
parse error on input `print'
when trying to print the object. If I instead try to print the value of the function without saving it with let it works just fine, but I need to save the object for later use.
How should the syntax be to make it work?
Also in the "else" part I get the following error:
The function `putStrLn' is applied to two arguments,
but its type `String -> IO ()' has only one
In the expression: putStrLn "Error" main
It thinks I try to run main as a parameter to putStrLn, but what I really want to do is first show the error and then run main again. How can I fix this?
Thanks in advance!
There's a few issues with your code.
Firstly, there's the parse error. There's two ways to fix this. One is by using a let .. in block, as #Lee points out.
let object = getObject parameter
in print object
Alternatively, we can just start another do block in the else clause:
then do
let object = getObject parameter
print object
Secondly, you're comparing the head of a string, to another string:
head parameter == "c"
getLine returns a string, so the head of a string is a character. We can just change this to
head parameter == 'c'
And finally, you're trying to do two statements in one block, similar to before in your else clause:
else
putStrLn "Error, try again"
main
If you want to chain together multiple statements, we have to use a do block, as before:
else do
putStrLn "Error, try again"
main
Putting it all together:
main = do
putStrLn "Enter parameter"
parameter <- getLine
if head parameter == 'c' then do
let object = getObject parameter
print object
else do
putStrLn "Error, try again"
main
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.
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.