Combining two readMaybe and getLine in haskell - haskell

In the haskell wikibook, one is asked to write something like this:
adding = do
putStrLn "enter first number"
first <- readMaybe <$> getLine
putStrLn "enter second number"
second <- readMaybe <$> getLine
let x = (+) <$> first <*> second :: Maybe Double
case x of
Just d -> putStrLn $ "the sum is " ++ show d
Nothing -> do
putStrLn "not good"
adding
This asks for two numbers and then adds them. However I tried to write it a little bit shorter, doing both getlines on the same line:
adding2 = do
putStrLn "enter two numbers"
x <- (+) <$> (readMaybe <$> getLine) <*> (readMaybe <$> getLine)
case x of
Just d -> putStrLn $ "the sum is " ++ show d
Nothing -> do
putStrLn "not good"
adding
but this doesn't work. It complains about the type of d, and I suspect it is because I haven't said anywhere that it should be a Maybe Double.
Or maybe it is the line (+) <$> (readMaybe <$> getLine) <*> (readMaybe <$> getLine)?
How can I fix this?
Apparently the problem is that I have two different monads and can't do what I'm hoping to do. I wrote one variant that I'm particularly happy with:
adding5 :: IO ()
adding5 = do
putStrLn "enter two numbers"
let a = readMaybe <$> getLine
a >>= \ a1 -> a >>= \ a2 ->
case (+) <$> a1 <*> a2 of
Just d -> putStrLn $ "the sum is " ++ show d
Nothing -> do putStrLn "not good"
adding5
Here is another one following amalloy's suggestion in the comments:
adding6 = do
putStrLn "enter two numbers"
[a1, a2] <- replicateM 2 (readMaybe <$> getLine)
case (+) <$> a1 <*> a2 of
Just d -> putStrLn $ "the sum is " ++ show d
Nothing -> do putStrLn "not good"
adding6

In the original function, you had
first :: Maybe Double
second :: Maybe Double
So when you combined them with <$> and <*> you were operating in a Maybe context: exactly what you wanted.
But in your new function, your second argument to <*> is
readMaybe <$> getLine :: Read t => IO (Maybe t)
You're now using <$> and <*> in the IO context, not the Maybe context, and so the things you are trying to add are of type Maybe t, not t. You can't add values of type Maybe t no matter what t is, because Maybe is not Num. So even if you give the compiler enough information to conclude that t should be Double, this won't work.
You have to either bind the IO values to local unwrapped names, as you did in your first function, or, equivalently, use >>= to operate inside of the IO (Maybe Double) you have.

Related

Could I make this Haskell more idiomatic?

I'm working on the HackerRank problem sets. The following Haskell correctly solves the problem, but I have a hunch that it's not what a seasoned veteran would write. I'd appreciate any input on how to make this prettier/more expressive.
compTrips :: [Int] -> [Int] -> [Int]
compTrips as bs =
let oneIfGreater a b = if a > b
then 1
else 0
countGreater x y = foldr (+) 0 $ zipWith oneIfGreater x y
in [countGreater as bs, countGreater bs as]
main = do
line1 <- getLine
line2 <- getLine
let alice = map read $ words line1
let bob = map read $ words line2
let (a:b:_) = compTrips alice bob
putStrLn $ show a ++ " " ++ show b
I don't like compTrips's type; it's too permissive. Declaring that it can return an [Int] says you don't know how many Ints it will return. But you know it will be exactly two; so:
compTrips :: [Int] -> [Int] -> (Int, Int)
In the implementation, I'd prefer sum to foldr (+) 0; it says more clearly what you intend, and also, as a side benefit, sum uses a strict left fold and so sometimes can be more memory-efficient:
compTrips as bs =
let oneIfGreater a b = if a > b
then 1
else 0
countGreater x y = sum $ zipWith oneIfGreater x y
in (countGreater as bs, countGreater bs as)
I don't like that we compute each comparison twice. I'd want to split out the computation of the comparison from the counting up of which way the comparison went. At the same time, I would switch over from using sum and your custom fromEnum implementation (namely, oneIfGreater) to using a combination of length and filter (or, in this case where we want both "sides" of the filter, partition). So:
import Data.Bifunctor
import Data.List
compTrips as bs = bimap length length . partition id $ zipWith (>) as bs
I think you can abstract the reading and parsing of a line so that this logic is not replicated. So:
readInts :: IO [Int]
readInts = do
line <- getLine
pure (map read $ words line)
main = do
alice <- readInts
bob <- readInts
let (a, b) = compTrips alice bob
putStrLn $ show a ++ " " ++ show b
I don't like read's type, for almost exactly the same reason I don't like compTrips's type. In this case, declaring that it can accept any String says it can parse anything, when in reality it can only parse a very specific language. readMaybe has a better type, saying that it may sometimes fail to parse:
readMaybe :: Read a => String -> Maybe a
There's a large collection of Applicative-based methods for combining the error-handling of many calls to readMaybe; especially check out traverse (which is a bit like map, but with the capability to handle errors) and liftA2 (which can convert any binary operation into one that can handle errors).
One way we could use it would be to print a nice error message when it failed, so:
import System.IO
import Text.Read
readInts = do
line <- getLine
case traverse readMaybe (words line) of
Just person -> pure person
Nothing -> do
hPutStrLn stderr "That doesn't look like a space-separated collection of numbers! Try again."
readInts
(Other error-handling options exist.)
That leaves us with the following final program:
import Data.Bifunctor
import Data.List
import System.IO
import Text.Read
compTrips :: [Int] -> [Int] -> (Int, Int)
compTrips as bs = bimap length length . partition id $ zipWith (>) as bs
readInts :: IO [Int]
readInts = do
line <- getLine
case traverse readMaybe (words line) of
Just person -> pure person
Nothing -> do
hPutStrLn stderr "That doesn't look like a space-separated collection of numbers! Try again."
readInts
main :: IO ()
main = do
alice <- readInts
bob <- readInts
let (a, b) = compTrips alice bob
putStrLn $ show a ++ " " ++ show b
Although the text is actually a bit longer, I would consider this a more idiomatic implementation; the main two things being to pay careful attention to avoiding the use of partial functions (like read and your partial pattern match on the result of compTrips) and increasing the reliance on library-provided bulk operations when handling lists (like our use of length and partition).
You can obain the number of elements that match with length . filter. So you can here count the number of matches with:
compTrips :: [Int] -> [Int] -> (Int, Int)
compTrips as bs = (f as bs, f bs as)
where f xs = length . filter id . zipWith (>) xs
It is usually better to return tuples to return "multiple" values. Since then these two values can have a different type, and furthermore the type checking mechansim will guarantee that you return two items.
You can perform fmap :: Functor f => (a -> b) -> f a -> f b or its operator synonym (<$>) :: Functor f => (a -> b) -> f a -> f b on the results of getLine to "post-process" the result:
main :: IO ()
main = do
line1 <- map read . words <$> getLine
line2 <- map read . words <$> getLine
let (a, b) = compTrips line1 line2
putStrLn (show a ++ " " ++ show b)
You may perhaps do like
λ> :{
λ| getLine >>= return . map (read :: String -> Int) . words
λ| >>= \l1 -> getLine
λ| >>= return . map (read :: String -> Int) . words
λ| >>= \l2 -> return $ zipWith (\a b -> if a > b then 1 else 0) l1 l2
λ| :}
1 2 3 4 5
2 3 1 4 2
[0,0,1,0,1]

fmap versus <$>

According to the documentation <$> is an synonym for fmap and they have the following types:
(<$>) :: Functor f => (a -> b) -> f a -> f b
fmap :: Functor f => (a -> b) -> f a -> f b
So it seems to me, from the signatures above, that they take the arguments in the same order, the only difference being that one is infix and the other is not. I have two pieces of code, one using fmap, and the other using <$>. Why is it that only the former runs correctly?
import Control.Applicative
main :: IO ()
main = do
[x, y] <- map read . words <$> getLine
putStrLn $ show (x + y)
This one won't compile:
import Control.Applicative
main :: IO ()
main = do
[x, y] <- map read . fmap words getLine
putStrLn $ show (x + y)
Precedence of operators is expressed as an integer between 0 and 9, while function application effectively has precedence 10 (higher than any operator).
(.) has very high precedence (9), while (<$>) has lower precedence (4), resulting in your first expression being parsed as
((map read) . words) <$> getLine
while your second expression is parsed as
(map read) . (fmap words getLine)
resulting in an attempt to compose an IO [String] value with a function of type [String] -> [a].
This is due to operator precedence. If we look at the source code, we see that:
infixl 4 <$>
infixr 9 .
So that means that if you write:
map read . words <$> getLine
Haskell sees this as:
(map read . words) <$> getLine
But if you write:
map read . fmap words getLine
Haskell sees this as:
(map read) . (fmap words getLine)
So the arguments of fmap are different.
If we however add brackets, the two will be equivalent:
import Control.Applicative
main :: IO ()
main = do
[x, y] <- map read . (words <$> getLine)
putStrLn $ show (x + y)

Adding an additional step to ">>="

I have this which works fine:
forM_ [1..10] $ \x -> myFunc1 x 99 >>= putStrLn . show >> return ()
myFunc1 :: Int -> Int -> IO Bool
myFunc1 .....
I want to do add an additional string to the output:
forM_ [1..10] $ \x -> myFunc1 x 99 >>= (++) "the result is: " >>= putStrLn . show >> return ()
But that doesn't compile. I've tried different variations but still no success. Your suggestions?
The first thing that sticks out is that the expression:
(++) "the result is: "
is not an IO-action - it's just a pure function String -> String, and
that's one reason why you code isn't type checking.
To turn it into an IO-action you can compose it with return:
return . ( (++) "the result is: " )
:: String -> IO String
Now you can use it with >>=.
However, that's not where the real problem is...
To prepend "the result is: " before the show you have to insert it in the putStrLn call:
... >>= putStrLn . (++ "the result is: ") . show
(Note that there is no need to >> return () since putStrLn already returns ()).
Honestly, this is a lot simpler using do-notation:
forM_ [1..10] $ \x -> do
s <- myFunc1 x 99
putStrLn $ "the result is: " ++ show s
You can compose show with the concatenation function like so:
forM_ [1..10] $ \x -> myFunc1 x 99 >>= putStrLn . ("the result is: " ++) . show >> return ()
Replace ++ with add
add x y = return (x ++ show (y))
forM_ [1..10] $ \x -> myFunc1 x 99 >>= add "the result is: " >>= putStrLn . show >> return ()
reason: The return type of ++ is not IO type

How to set getLine to only receive strings that can be converted to numbers?

main = do
putStrLn "Hello,Name please?"
first <- getLine
second <- getLine
third <- getLine
if (second == "divide") then putStrLn (show (read first ::Double )/ (read third :: Double))
else putStrLn "Cannot do"
So i want a number in first and third variables,and the second variable will be given the work divide and the if statement will begin and convert the String in first to a Double.But i think the issue is that the variable First is expecting a String like "one" that cannot be converted to a Double.How to fix this to allow getLine to only receive Strings that can be changed in numbers
Here's a quick idea that could help you:
import Control.Applicative
import Data.Char
getNum :: (Num a, Read a) => IO (Maybe a)
getNum = do
(x, xs) <- break (not . isDigit ) <$> getLine
case xs of
[] -> return $ Just $ read x
_ -> return Nothing
main :: IO ()
main = do
x <- getNum :: IO (Maybe Double)
case x of
Nothing -> do
putStrLn "Not a number, try again"
main
Just x' ->
putStrLn $ "Your number: " ++ show x'
return ()
You cannot "make" getLine accept only numbers, you make a function that does. You can even modify this function to accept only the first set of numbers before a non-digit (right now it returns Nothing if any non-digit is in the string).
Update
Here's a program that asks for two numbers and an operation, doing some value checking:
import Control.Applicative
import Data.Char
import Data.Maybe
getNum :: (Num a, Read a) => IO (Maybe a)
getNum = do
(x, xs) <- break (not . isDigit ) <$> getLine
case xs of
[] -> return $ Just $ read x
_ -> return Nothing
main :: IO ()
main = do
putStr "First: "
x <- getNum :: IO (Maybe Double)
putStr "Operation: "
op <- getLine
putStr "Second: "
y <- getNum :: IO (Maybe Double)
if isJust x && isJust y then do
let
x' = fromJust x
y' = fromJust y
putStr "Result: "
case op of
"divide" -> putStrLn $ show $ x' / y'
"sum" -> putStrLn $ show $ x' + y'
_ -> putStrLn "No operation"
else
putStrLn "Invalid numbers"
return ()

Simple example on do syntax transformed to >>= syntax

How can reverse2lines transformed into the using >>= syntax ? Just like addOneInt is transformed into addOneInt'?
addOneInt :: IO ()
addOneInt = do line <- getLine
putStrLn (show (1 + read line :: Int))
addOneInt' :: IO ()
addOneInt' = getLine >>= \line ->
putStrLn (show ( 1 + read line :: Int))
reverse2lines :: IO ()
reverse2lines =
do line1 <- getLine
line2 <- getLine
putStrLn (reverse line2)
putStrLn (reverse line1)
Please also consider reading the follow up question and its valuable answers.
You could safely deduce it from what you already know. Start by fully parenthesizing your addOneInt':
addOneInt' = getLine >>= (\line ->
putStrLn (show (1 + read line :: Int)) )
Next, you reverse2lines can be equivalently written as
reverse2lines =
do { line1 <- getLine ;
do { line2 <- getLine ;
do { putStrLn (reverse line2) ;
do { putStrLn (reverse line1) } } } }
Applying the one-step transformation that you already used for addOneInt, as much times as needed, you'd get
reverse2lines' :: IO ()
reverse2lines' =
getLine >>= (\line1 ->
getLine >>= (\line2 ->
putStrLn (reverse line2) >>
putStrLn (reverse line1) ) )
Syntactically, the parentheses around lambda expressions can be omitted, but writing them explicitly here helps to clarify and make obvious the nested structure of these functions, especially if we'd line up the indentation.
Full translation arranges for the monad's fail function to be called on pattern mismatch, but since the variable patterns (line1, line2) are irrefutable, this translation is in fact exact.
Statements of type x <- m can be translated as m >>= \x -> ..., and statements in which the return value isn't bound like m can be translated as m >> ...
reverse2lines :: IO ()
reverse2lines = getLine
>>= \line1 -> getLine
>>= \line2 -> putStrLn (reverse line2)
>> putStrLn (reverse line1)
The translation isn't exact - the fail monad function complicates things a little. You can read about it here, but as far as I know it's irrelevant when dealing with the IO monad.
Here is the code from Will's very nice answer using only >>= :
reverse2lines' :: IO ()
reverse2lines' =
getLine >>= (\line1 ->
getLine >>= (\line2 ->
putStrLn (reverse line2) >>= (\_ ->
putStrLn (reverse line1) )))
Lesson learned : the sequencing in do corresponds simply to nesting of lambda expressions.

Resources