Question

///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..

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top