I am trying to read lines from input in Haskell until I find a non-empty line. Actually, I know how to do it simply using the following code:
notEmpty [] = return ""
notEmpty (l:xs) = do
s <- l
if s /= "" then return s
else notEmpty xs
getLine' = notEmpty $ repeat getLine
Test (I typed two empty lines then 'foo'):
*> getLine'
foo
"foo"
However, for the sake of exercise, I am trying to achieve this using Monoids (http://learnyouahaskell.com/functors-applicative-functors-and-monoids#monoids), trying to mimick the First/getFirst Monoid (see link).
I first created a Monoid on lists that fits my needs (concatenation only keeps the first argument):
newtype FirstSt a = FirstSt { getFirstSt :: [a] }
deriving (Eq, Ord, Read, Show)
instance Monoid (FirstSt a) where
mempty = FirstSt []
FirstSt [] `mappend` x = x
FirstSt s `mappend` _ = FirstSt s
Which works well on a infinite list of strings (thanks to laziness):
> getFirstSt . mconcat . map FirstSt $ ["", "", "foo", "", "bar"] ++ repeat ""
"foo"
However, I can't get it to work in the IO Monad. I tried the following:
ioFirstSt = (=<<) (return . FirstSt)
getLine'' = getFirstSt <$> mconcat <$> (sequence . map ioFirstSt $ repeat getLine)
Which has the correct type:
*> :t getLine''
getLine'' :: IO [Char]
However, Haskell keeps wanting to evaluate the whole list before giving it to mconcat
...
In there a way to keep laziness while navigating in the Monoid/Monad scope?