Could I make this Haskell more idiomatic? - haskell

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]

Related

Is there a more readable way of rewriting this pure function to use the Writer Monad?

I wrote a recursive algorithm using a list comprehension to perform the recursion. I thought my code was clear and easy to read, and the results it produced were correct.
However, I found it difficult to understand the performance of my code on certain inputs. I thought it would be useful to use the Writer monad to put some logging into my code.
I found it quite difficult to convert my non-monadic code to monadic. Eventually I got it to compile and run correctly, but the monadic code is much less understandable than the original.
The original problem is too complex to explain here, so I have written a toy example that shows the non-monadic and monadic approaches, but doesn't actually calculate anything useful!
So my question is: is there a better way to write the function fMonadic, such that it is more readable?
import Control.Monad (forM)
import Control.Monad.Writer (Writer, runWriter, tell)
fun :: Int -> [[Int]] -> [[Int]]
fun a b = map (map (a +)) b
fNonMonadic :: [[Int]] -> [[Int]]
fNonMonadic [] = [[]]
fNonMonadic (first : rest) =
[ first ++ s
| e <- first
, s <- fNonMonadic $ fun e rest]
fMonadic :: [[Int]] -> Writer [String] [[Int]]
fMonadic [] = do
tell ["base case"]
return [[]]
fMonadic (first : rest) =
fmap concat . forM first $ \ e -> do
tell ["recursive case " ++ show e]
fmap (map (first ++)) $ fMonadic $ fun e rest
main = do
let arg = [[0, 1], [20, 30], [400, 500]]
print $ fNonMonadic arg
let (a, b) = runWriter $ fMonadic arg
print a
mapM_ putStrLn b
It is often awkward to equip pure Haskell functions, which are structured in an algebraic, highly branched tree manner, with a monadic feature such as logging, which requires a more “imperative” structure. However, sometimes it is actually quite natural to write even pure computations using monadic combinators, and yours is in fact one of these. Namely, the list comprehension that's at the heart of fNonMonadic is already basically using the list monad; it can be written thus:
type ListM = [] -- Just to distinguish where I use list as a monad
fNonMonadic :: [[Int]] -> ListM [Int]
fNonMonadic [] = return []
fNonMonadic (first : rest) = do
e <- first
s <- fNonMonadic $ fun e rest
return $ first ++ s
Starting from that, it is much easier to add the writer functionality, by using it as the base of a monad transformer stack. The list must then be used in transformer shape, too:
import Control.Monad.Trans.List
fMonTrafo :: [[Int]] -> ListT (Writer [String]) [Int]
fMonTrafo [] = do
lift $ tell ["base case"]
return []
fMonTrafo (first : rest) = do
e <- ListT $ pure first
lift $ tell ["recursive case " ++ show e]
s <- fMonTrafo $ fun e rest
return $ first ++ s
You may notice that the documentation of ListT warns that the base monad should be commutative, which Writer actually isn't – the order of log entries may get messed up. I don't know if this matters here. If yes, check out the alternative implementation suggested by Daniel Wagner.
I looked at several alternatives to Control.Monad.Trans.List, and soon settled on module ListT from Volkov's list-t package.
This gives the same result as my ugly fMonadic function, but more readable code. It also works correctly, and leads to readable code, in the real problem I want to solve.
In the real problem, the ListT-based code runs slightly more slowly than the ugly code, but the difference isn't enough to matter.
Many thanks again for leftaroundabout's help with this.
For reference, here is the revised version of the toy example, doing the computation the three different ways and showing that the answer is the same:
import Control.Monad (forM)
import ListT (ListT, fromFoldable, toList)
import Control.Monad.Writer (Writer, lift, runWriter, tell)
fun :: Int -> [[Int]] -> [[Int]]
fun a b = map (map (a +)) b
fNonMonadic :: [[Int]] -> [[Int]]
fNonMonadic [] = [[]]
fNonMonadic (first : rest) = do
e <- first
s <- fNonMonadic $ fun e rest
return $ first ++ s
-- The above do notation means the same as this list comprehension:
-- [ first ++ s
-- | e <- first
-- , s <- fNonMonadic $ fun e rest]
fMonadic :: [[Int]] -> Writer [String] [[Int]]
fMonadic [] = do
tell ["base case"]
return [[]]
fMonadic (first : rest) =
fmap concat . forM first $ \ e -> do
tell ["recursive case " ++ show e]
fmap (map (first ++)) $ fMonadic $ fun e rest
fMonTrafo :: [[Int]] -> ListT (Writer [String]) [Int]
fMonTrafo [] = do
lift $ tell ["base case"]
return []
fMonTrafo (first : rest) = do
e <- fromFoldable first
lift $ tell ["recursive case " ++ show e]
s <- fMonTrafo $ fun e rest
return $ first ++ s
main = do
let arg = [[0, 1], [20, 30], [400, 500]]
let x = fNonMonadic arg
print x
let (a, b) = runWriter $ fMonadic arg
print a
mapM_ putStrLn b
let (c, d) = runWriter $ toList $ fMonTrafo arg
print c
mapM_ putStrLn d
putStrLn $ if x == a then "fNonMonadic == fMonadic" else error ""
putStrLn $ if x == c then "fNonMonadic == fMonTrafo" else error ""
putStrLn $ if b == d then "fMonadic log == fMonTrafo log" else error ""

Lazy list wrapped in IO

Suppose the code
f :: IO [Int]
f = f >>= return . (0 :)
g :: IO [Int]
g = f >>= return . take 3
When I run g in ghci, it cause stackoverflow. But I was thinking maybe it could be evaluated lazily and produce [0, 0, 0] wrapped in IO. I suspect IO is to blame here, but I really have no idea. Obviously the following works:
f' :: [Int]
f' = 0 : f'
g' :: [Int]
g' = take 3 f'
Edit: In fact I am not interested in having such a simple function f, original code looked more along the lines:
h :: a -> IO [Either b c]
h a = do
(r, a') <- h' a
case r of
x#(Left _) -> h a' >>= return . (x :)
y#(Right _) -> return [y]
h' :: IO (Either b c, a)
-- something non trivial
main :: IO ()
main = mapM_ print . take 3 =<< h a
h does some IO computations and stores invalid (Left) responses in a list until a valid response (Right) is produced. The attempt is to construct the list lazily even though we are in the IO monad. So that someone reading the result of h can start consuming the list even before it is complete (because it may even be infinite). And if the one reading the results cares only for the first 3 entries no matter what, the rest of the list does not even have to be constructed. And I am getting the feeling that this will not be possible :/.
Yes, IO is to blame here. >>= for IO is strict in the "state of the world". If you write m >>= h, you'll get an action that first performs the action m, then applies h to the result, and finally performs the action h yields. It doesn't matter that your f action doesn't "do anything"; it has to be performed anyway. Thus you end up in an infinite loop starting the f action over and over.
Thankfully, there is a way around this, because IO is an instance of MonadFix. You can "magically" access the result of an IO action from within that action. Critically, that access must be sufficiently lazy, or you'll throw yourself into an infinite loop.
import Control.Monad.Fix
import Data.Functor ((<$>))
f :: IO [Int]
f = mfix (\xs -> return (0 : xs))
-- This `g` is just like yours, but prettier IMO
g :: IO [Int]
g = take 3 <$> f
There's even a bit of syntactic sugar in GHC for this letting you use do notation with the rec keyword or mdo notation.
{-# LANGUAGE RecursiveDo #-}
f' :: IO [Int]
f' = do
rec res <- (0:) <$> (return res :: IO [Int])
return res
f'' :: IO [Int]
f'' = mdo
res <- f'
return (0 : res)
For more interesting examples of ways to use MonadFix, see the Haskell Wiki.
It sounds like you want a monad that mixes the capabilities of lists and IO. Luckily, that's just what ListT is for. Here's your example in that form, with an h' that computes the Collatz sequence and asks the user how they feel about each element in the sequence (I couldn't really think of anything convincing that fit the shape of your outline).
import Control.Monad.IO.Class
import qualified ListT as L
h :: Int -> L.ListT IO (Either String ())
h a = do
(r, a') <- liftIO (h' a)
case r of
x#(Left _) -> L.cons x (h a')
y#(Right _) -> return y
h' :: Int -> IO (Either String (), Int)
h' 1 = return (Right (), 1)
h' n = do
putStrLn $ "Say something about " ++ show n
s <- getLine
return (Left s, if even n then n `div` 2 else 3*n + 1)
main = readLn >>= L.traverse_ print . L.take 3 . h
Here's how it looks in ghci:
> main
2
Say something about 2
small
Left "small"
Right ()
> main
3
Say something about 3
prime
Left "prime"
Say something about 10
not prime
Left "not prime"
Say something about 5
fiver
Left "fiver"
I suppose modern approaches would use pipes or conduits or iteratees or something, but I don't know enough about them to talk about the tradeoffs compared to ListT.
I'm not sure if this is an appropriate usage, but unsafeInterleaveIO would get you the behavior you're asking for, by deferring the IO actions of f until the value inside of f is asked for:
module Tmp where
import System.IO.Unsafe (unsafeInterleaveIO)
f :: IO [Int]
f = unsafeInterleaveIO f >>= return . (0 :)
g :: IO [Int]
g = f >>= return . take 3
*Tmp> g
[0,0,0]

In Haskell, I want to make simple concordancer. But it response errors again, and again

import System.IO
import Data.List
import Data.Char
printlist :: Show a => a -> IO ()
printlist x = putStr (show x)
main = do
handle <- openFile "/usr/local/share/corpus" ReadMode
text <- hGetContents handle
let wlist = words text
clist = map (\k -> take ((k + 15) - k + 1).drop (k - 10))(elemIndices "word" wlist)
printlist clist
What can I do to finish my job.
please, give me a answer or hints
Well I felt nice, so I fixed up the errors here
import Data.List
printlist :: Show a => a -> IO ()
printlist = putStr . show
main = do
text <- readFile "/usr/local/share/corpus" -- removed useless handle
let clist = zipWith (flip ($)) (repeat text)
-- ^ applied each function to file
-- since you currently had
-- clist :: [String -> String]
. map (\k -> take 16 . drop (k-10))
. elemIndices "word"
$ words text -- inlined wlist
printlist clist -- fixed indenting
So now what this does is produce a list of functions of type String -> String and apply each of them to the file /usr/local/share/corpus and print the result.
I suppose the map part can be rewritten to be
(.:) = (.) . (.)
infixr 9 .:
map (take 16 .: drop . subtract 10)
Which is arguably prettier.

Haskell way to join [IO String] into IO String

my goal is to write Haskell function which reads N lines from input and joins them in one string. Below is the first attempt:
readNLines :: Int -> IO String
readNLines n = do
let rows = replicate n getLine
let rowsAsString = foldl ++ [] rows
return rowsAsString
Here haskell complaints on foldl:
Couldn't match expected type [a]'
against inferred type(a1 -> b -> a1)
-> a1 -> [b] -> a1'
As I understand type of rows is [IO String], is it possible some how join such list in a single IO String?
You're looking for sequence :: (Monad m) => [m a] -> m [a].
(Plus liftM :: Monad m => (a1 -> r) -> m a1 -> m r and unlines :: [String] -> String, probably.)
Besides what ephemient points out, I think you have a syntax issue: The way you're using the ++ operator makes it look like you are trying to invoke the ++ operator with operands foldl and []. Put the ++ operator in parentheses to make your intent clear:
foldl (++) [] rows
The functions you are looking for is is sequence, however it should be noted that
sequence (replicate n f)
is the same as
replicateM n f
And foldl (++) [] is equivalent to concat. So your function is:
readNLines n = liftM concat (replicateM n getLine)
Alternatively if you want to preserve line breaks:
readNLines n = liftM unlines (replicateM n getLine)
The shortest answer I can come up with is:
import Control.Applicative
import Control.Monad
readNLines :: Int -> IO String
readNLines n = concat <$> replicateM n getLine
replicate returns a list of IO String actions. In order to perform these actions, they need to be run in the IO monad. So you don't want to join an array of IO actions, but rather run them all in sequence and return the result.
Here's what I would do
readNLines :: Int -> IO String
readNLines n = do
lines <- replicateM n getLine
return $ concat lines
Or, in applicative style:
import Control.Applicative
readNLines :: Int -> IO String
readNLines n = concat <$> replicateM n getLine
Both of these use the monadic replicate (replicateM), which evaluates a list of monadic values in sequence, rather than simply returning a list of actions

Haskell -- problem with pretty-printing a list

I'm new to haskell, and i read through and digested Learn You A Haskell For Great Good, trying out a couple of things along the way. For my first project i wanted to try the classic: FizzBuzz. So i came up with the following code:
import System.IO
fizzBuzz :: (Integral a) => a -> String
fizzBuzz num
| fizz && buzz = "FizzBuzz"
| fizz = "Fizz"
| buzz = "Buzz"
| otherwise = show num
where fizz = num `mod` 3 == 0
buzz = num `mod` 5 == 0
main = print $ map fizzBuzz [1..100]
Worked great, except i got a rather dense looking list that was hard to read. So i tried this main function instead:
main = map putStrLn $ map fizzBuzz [1..100]
And that gives me the error Couldn't match expected type 'IO t' against inferred type '[IO ()]'. I tried half a dozen things and none of it seemed to help. What's the proper way to do what i'm trying to do?
map :: (a -> b) -> [a] -> [b]
putStrLn :: Show a => a -> IO ()
map putStrLn :: Show a => [a] -> [IO ()]
You've got a list of IO () actions.
main :: IO ()
You need to join them into a single IO () action.
What you want to do is to perform each of those IO () actions in sequence/sequence_:
sequence :: Monad m => [m a] -> m [a]
sequence_ :: Monad m => [m a] -> m ()
For convenience, mapM/mapM_ will map a function over a list and sequence the resulting monadic results.
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM_ :: Monad m => (a -> m b) -> [a] -> m ()
So your fixed code would look like this:
main = mapM_ putStrLn $ map fizzBuzz [1..100]
Although I'd probably write it like this:
main = mapM_ (putStrLn . fizzBuzz) [1..100]
Or even this:
main = putStr $ unlines $ map fizzBuzz [1..100]
Let's write our own sequence. What do we want it to do?
sequence [] = return []
sequence (m:ms) = do
x <- m
xs <- sequence ms
return $ x:xs
If there's nothing left in the list, return (inject into the monad) an empty list of results.
Otherwise, within the monad,
Bind (for the IO monad, this means execute) the first result.
sequence the rest of the list; bind that list of results.
Return a cons of the first result and the list of other results.
GHC's library uses something more like foldr (liftM2 (:)) (return []) but that's harder to explain to a newcomer; for now, just take my word that they're equivalent.
sequence_ is easier, since it doesn't bother keeping track of the results. GHC's library implements it as sequence_ ms = foldr (>>) (return ()) ms. Let's just expand the definition of foldr:
sequence [a, b, c, d]
= foldr (>>) (return ()) [a, b, c, d]
= a >> (b >> (c >> (d >> return ())))
In other words, "do a, discard the result; do b; discard the result, … finally, return ()".
mapM f xs = sequence $ map f xs
mapM_ f xs = sequence_ $ map f xs
On the other hand, you don't even need to know monads at all with the alternate unlines solution.
What does unlines do? Well, lines "a\nb\nc\nd\n" = ["a", "b", "c", "d"], so of course unlines ["a", "b", "c", "d"] = "a\nb\nc\nd\n".
unlines $ map fizzBuzz [1..100] = unlines ["1", "2", "Fizz", ..] = "1\n2\nFizz\n..." and off it goes to putStr. Thanks to the magic of Haskell's laziness, the full string never needs to be constructed in memory, so this will happily go to [1..1000000] or higher :)

Resources