Haskell: Basic Reading Int - haskell

The objective is to code the game of Nim in Haskell as a school assignment. I'm new to Haskell and get weird behavior when I try to read input.
The goal is to read two integers. Instead of printing the first message, then prompting and then going on to the second one, it just prints the two messages and I'm unable to give proper input. What is wrong here?
type Board = [Int] -- The board
type Heap = Int -- id of heap
type Turn = (Int, Int) -- heap and number of stars to remove
readInt :: String -> IO Int
readInt msg = do putStr (msg ++ "> ")
inp <- getChar
let x = ord inp
return x
readTurn :: Board -> IO(Turn)
readTurn b = do heap <- readInt "Select heap:"
amt <- readInt "Select stars:"
print heap
print amt
return(heap, amt)

The problem is that stdout is line-buffered by default, which means that nothing gets output until you print a newline. There are two ways to solve this:
Use hFlush stdout after printing the prompt to flush the buffer.
Use hSetBuffering stdout NoBuffering at the start of your program to disable output buffering.
Also, using getChar and ord will read a single character and give you its ASCII value, which is probably not what you wanted. To read and parse a number, use readLn:
import System.IO (hFlush, stdout)
readInt :: String -> IO Int
readInt msg = do
putStr (msg ++ "> ")
hFlush stdout
readLn

readChar reads only one character at a time. I assume you want instead to read a whole line, convert it to a number (possibly with more than one digit), and continue. You need to use getLine :: IO String and read :: Read a => String -> a:
readInt :: String -> IO Int
readInt msg = do
putStr (msg ++ "> ")
hFlush stdout
inp <- getLine
return (read inp)

Related

Concurrent Haskell: Acting upon the data within a custom data type

So far in this program I have a custom data type, Msg which is a C Char. In the program, a separate thread is created and a separate function userThread listens for a keystroke and stores the char sent in an MVar that's acting as a simple communication channel between main and userThread
I have gotten to the point, using deriving (Show) where I'm able to print the Msg as to the terminal, like a string. If the user types j the output is:
C 'j'
(Note it also prints C when I only want the char itself)
I have an existing list and my aim is if the char is NOT in that list, then add it. If it IS, then don't add it and remove all instances of that char in the list.
For example, if a user typed c, then
[a,b] becomes [a,b,c]
but
[f,c,c,h,c] becomes [f,h]
My current code is below:
module Main where
import Control.Concurrent
import Control.Monad
import System.IO
import System.Random
import Text.Printf
data Msg = C Char deriving (Show)
main :: IO ()
main = loop
where
loop = do
hSetBuffering stdout NoBuffering
hSetBuffering stdin NoBuffering
hSetEcho stdin False
--already existing list
let x = [1, 's', 'g', 4 ,5]
chan <- newEmptyMVar
forkIO $ userThread (chan)
r <- takeMVar (chan)
putStrLn $ show x
print r
-- Listens for keystrokes from the user, the passes them to the main thread
userThread :: MVar Msg -> IO ()
userThread chan = do
c <- getChar
putMVar chan (C c)
I'm confused as I'm able to perform actions like Print on my Msg just fine because its actual content is a char. But when I try to ad it to a list using r:x the compile error says:
* Couldn't match type `Char' with `Msg'
Expected type: [Msg]
Actual type: [Char]
How can I make my Msg fit into this list? Thanks very much for your help.
The problem seams to be that your list does not contain C Char but rather Char. This may be fixed by doing let x = [C '1',C 's',C 'g',C '4',C '5'].
Your userThread puts a C Char into chan, so when you unpack it with takeMVar (chan) and bind it to r, r now has type Msg. Because in Haskell lists can only hold values of the same type, you can not prepend an Msg to a [Char], therefor the error. Make all the elements of x of type Msg and all will be good (hopefully...). Alternatively you can implement a
toChar :: Msg -> Char
toChar C c = c
and do something like (toChar r):x if you insist on having Char in your list.
for remvoing elements from x see this. you can then write something of the sort:
if elem r x then newX = remove r x else newX = r:x --if x contains Msg
if elem cr x then newX = remove cr x else newX = cr:x
where cr = toChar r --if you insist on having chars in x...
hope that helps.

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.

How can I use getLine or getChar in haskell?

I'm trying to get from my console a string or just a char and store into a variable.
I tried to use:
> let x = getChar
> x
> c -- for getting a char.
But nothing is stored (same for getLine) how can I do?
The type of getChar is IO Char. It is not a function that returns a Char; it is an IO action that, when executed, returns a Char. (While subtle, this distinction is crucial to understanding how Haskell performs IO with pure functions.)
The line
let x = getChar
just binds the name x to the same IO action (which you can see by subsequently typing :t x in GHCi). Typing x then executes that action; GHCI waits for you to type a character, then it immediately returns that character.
To use getChar in a program, you need to use it within an IO monad, with something like
main = do ch <- getChar
print ch
or
main = getChar >>= print
Here is a sample
main = do
x <- getLine
putStrLn $ "Here is the string you typed in: " ++ x
Reading from console, maybe is not very useful. However you should use <- construct.
For example (without " is good too) :
>myString <- getLine
>"Hello world"
or
>myChar <- getChar
>c
For more I suggest to read here
You need to bind it to a variable using <-, the result of an action is being bound:
*Main> variable <- getLine
hello
*Main> putStrLn variable
hello
*Main> anotherChar <- getChar
a*Main>
*Main> putChar anotherChar
a*Main>
Function getLine has type IO String and getChar has type IO Char.

Stop program from crashing when entering not-numbers in a menu with numbered options

My search function works properly when the input is a number, but crashes when it is not. What can I add to my code to prevent that from happening?
searchAge = do
putStrLn "\n Please type the age of the person you're looking for: \n"
age <- getLine
input <- readFile $ "databas.txt"
putStrLn "\n Here are all the people that matches your search: \n"
let people = parse input
output = map personToString (filter (\p -> personAge p == read age) people)
in putStrLn (unlines output)
putStrLn "Let's get back to the search menu again!"
searchDatabase
Listen up, oh younger one,
as this is a maddening song.
Take a look and you will see
read :: String -> Int is wrong for ye.
Its type is wrong, its result is unknown;
if been used on a string like "frown".
But here is for the final hint
you're looking for readMaybe :: String -> Maybe Int.
The problem with read is that there is no notion of failure in its type signature:
read :: Read a => String -> a
What happens if we set a to Int and try to use it on the string "frown"? It will result in an exception:
ghci> read "frown" :: Int
*** Exception: Prelude.read: no parse
After all, what should it return? Anything from the domain of Int is a valid value.
Enter readMaybe :: Read a => String -> Maybe a. Now the potential error is covered in the type signature, and it does not result in an exception anymore:
ghci> import Text.Read
ghci> readMaybe "frown" :: Maybe Int
Nothing
ghci> readMaybe "15" :: Maybe Int
Just 15
We can wrap this in a new function called getNumber that repeatedly asks for an Int until the user actually provides one:
getNumber :: IO Int
getNumber = do
line <- getLine
case (readMaybe line :: Maybe Int) of
Just x -> return x
Nothing -> putStrLn "Please enter a number!" >> getNumber
You can then use it to get an Int instead of a String for your age:
searchAge = do
...
age <- getNumber
input <- readFile "databas.txt"
putStrLn "\n Here are all the people that matches your search: \n"
let people = parse input
output = map personToString (filter (\p -> personAge p == age) people)
in putStrLn (unlines output)

Extract records from a file - Haskell

///Edit
I have a problem with haskell. If someone can help me, that would be great.
I'm inserting records into a file using the following code:
check :: Int -> Int
check a
|a > 0 && a<=10 = a
|otherwise = error "oh. hmm.. enter a number from the given interval"
ame :: IO ()
ame = do
putStr "Enter the file name: "
name <- getLine
putStrLn "Do you want to add new records? "
question <- getLine
if question == "yes" then do
putStrLn "Enter your records:"
newRec <- getLine
appendFile name ('\n':newRec)
--new lines--
putStrLn "enter a number between 0 and 10: "
something <- getLine
return (read something:: Int)
let response = check something
putStrLn response
appendFile name ('\n':something)
putStrLn "enter something new again: "
something2 <- getLine
appendFile name ('\n':something2)
putStrLn "a"
else
putStr "b"
Now I want to extract some records from this file, using a specific criteria. For example, i want to extract and display records from even(or odd or any other criteria) rows. can I do that? if yes, how?
Also...
I want to check the user input. Let's say that I don't want him/her to enter a string instead of an integer. can I also check his/her input? do I need to create another function and call that function inside the code from above?
///Edit
thank you for answering. but how can i embed it into my previous code?
I've tried now to create a function(you can see the above code) and then call that function in my IO. but it doesn't work..
Yes, it is certainly possible to display only certain rows. If you want to base it off of the row number, the easiest way is to use zip and filter
type Record = String
onlyEven :: [Record] -> [Record]
onlyEven records =
map snd $ -- Drop the numbers and return the remaining records
filter (even . fst) $ -- Filter by where the number is even
zip [1..] -- All numbers
records -- Your records
This technique can be used in a lot of circumstances, you could even abstract it a bit to
filterByIdx :: Integral i => (i -> Bool) -> [a] -> [a]
filterByIdx condition xs = map snd $ filter (condition . fst) $ zip [1..] xs
-- Could also use 0-based of `zip [0..] xs`, up to you
onlyEven :: [a] -> [a]
onlyEven = filterByIdx even
If you want to check if an input is an Int, the easiest way is to use the Text.Read.readMaybe function:
import Text.Read (readMaybe)
promptUntilInt :: IO Int
promptUntilInt = do
putStr "Enter an integer: "
response <- getLine
case readMaybe response of
Just x -> return x
Nothing -> do
putStrLn "That wasn't an integer!"
promptUntilInt
This should give you an idea of how to use the function. Note that in some cases you'll have to specify the type signature manually as case (readMaybe response :: Maybe Int) of ..., but here it'll work fine because it can deduce the Int from promptUntilInt's type signature. If you get an error about how it couldn't figure out which instance for Read a to use, you need to manually specify the type.
You have
something <- getLine
return (read something:: Int)
let response = check something
putStrLn response
To step through what you're trying to do with these lines:
something <- getLine
getLine has the type IO String, meaning it performs an IO action and returns a String. You can extract that value in do notation as
something <- getLine
Just as you have above. Now something is a String that has whatever value was entered on that line. Next,
return (read something :: Int)
converts something to an Int, and then passes it to the function return. Remember, return is not special in Haskell, it's just a function that wraps a pure value in a monad. return 1 :: Maybe Int === Just 1, for example, or return 1 :: [Int] === [1]. It has contextual meaning, but it is no different from the function putStrLn. So that line just converts something to an Int, wraps it in the IO monad, then continues on to the next line without doing anything else:
let response = check something
This won't compile because check has the type Int -> Int, not String -> String. It doesn't make any sense to say "hello, world" > 0 && "hello, world" <= 10, how do you compare a String and an Int? Instead, you want to do
let response = check (read something)
But again, this is unsafe. Throwing an error on an invalid read or when read something is greater than 10 will crash your program completely, Haskell does errors differently than most languages. It's better to do something like
check :: Int -> Bool
check a = a > 0 && a <= 10
...
something <- getLine
case readMaybe something of
Nothing -> putStrLn "You didn't enter a number!"
Just a -> do
if check a
then putStrLn "You entered a valid number!"
else putStrLn "You didn't enter a valid number!"
putStrLn "This line executes next"
While this code is a bit more complex, it's also safe, it won't ever crash and it handles each case explicitly and appropriately. By the way, the use of error is usually considered bad, there are limited capabilities for Haskell to catch errors thrown by this function, but errors can be represented by data structures like Maybe and Either, which give us pure alternatives to unsafe and unpredictable exceptions.
Finally,
putStrLn response
If it was able to compile, then response would have the type Int, since that's what check returns. Then this line would have a type error because putStrLn, as the name might suggest, puts a string with a new line, it does not print Int values. For that, you can use print, which is defined as print x = putStrLn $ show x
Since this is somewhat more complex, I would make a smaller function to handle it and looping until a valid value is given, something like
prompt :: Read a => String -> String -> IO a
prompt msg failMsg = do
putStr msg
input <- getLine
case readMaybe input of
Nothing -> do
putStrLn failMsg
prompt
Just val -> return val
Then you can use it as
main = do
-- other stuff here
-- ...
-- ...
(anInt :: Int) <- prompt "Enter an integer: " "That wasn't an integer!"
-- use `anInt` now
if check anInt
then putStrLn $ "Your number multiplied by 10 is " ++ show (anInt * 10)
else putStrLn "The number must be between 1 and 10 inclusive"
You don't have to make it so generic, though. You could easily just hard code the messages and the return type like I did before with promptUntilInt.

Resources