Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterate inside a do block

Tags:

haskell

monads

I wrote this code:

toCouplesFile = do inputFile <- openFile "deletedId.csv" ReadMode
                   outputFile <- openFile "couples.txt" WriteMode
                   readAndChange inputFile outputFile

readAndChange i o = do iseof <- hIsEOF i
                       if iseof then (return o)
                       else do line <- hGetLine i
                               hPutStrLn o (show (extractNameAndId line))
                               readAndChange i o

I wonder if I can rewrite this code using just one function, using something similar to this pattern:

function x = do ...
                label
                .....
                if ... then label else exit
like image 978
Aslan986 Avatar asked Nov 30 '22 14:11

Aslan986


2 Answers

You're making life difficult by programming in a needlessly imperative way. You're programming in the beautiful Haskell language and you're looking for a goto construct!

Why not just import Control.Applicative (<$>) and write

readAndChange' = writeFile "couples.txt" =<< 
    unlines.map (show.extractNameAndId).lines <$> readFile "deletedId.csv" 

(Yup, that's almost a one-liner. It's in clean, functional style and uncluttered by the mechanics of reading and writing lines. As much as possible of the processing is done in pure code, only input and output are IO-based.)

Explanation:

Here unlines.map (show.extractNameAndId).lines processes your input by chopping it into lines, applying extractNameAndId then show to each one using map, then joining them back together again with unlines.

unlines.map (show.extractNameAndId).lines <$> readFile "deletedId.csv" will read the file and apply the processing function. <$> is pleasant syntax for fmap.

writeFile "couples.txt" =<< getanswer is the same as getanswer >>= writeFile "couples.txt" - get the answer as above then write it to the file.

Try writing greet xs = "hello " ++ xs then in ghci do these for fun

greet "Jane"        -- apply your pure function purely
greet $ "Jane"      -- apply it purely again
greet <$> ["Jane","Craig","Brian"]  -- apply your function on something that produces three names
greet <$> Just "Jane"               -- apply your function on something that might have a name
greet <$> Nothing                   -- apply your function on something that might have a name
greet <$> getLine                   -- apply your function to whatever you type in 
greet <$> readFile "deletedId.csv"  -- apply your function to your file 

the final one is how we used <$> in readAndChange. If there's a lot of data in deletedId.csv you'll miss the hello, but of course you can do

greet <$> readFile "deletedId.csv" >>= writeFile "hi.txt"
take 4.lines <$> readFile "hi.txt"

to see the first 4 lines.

So $ lets you use your function on the arguments you gave it. greet :: String -> String so if you write greet $ person, the person has to be of type String, whereas if you write greet <$> someone, the someone can be anything that produces a String - a list of Strings, an IO String, a Maybe String. Technically, someone :: Applicative f => f String, but you should read up on type classes and Applicative Functors first. Learn You a Haskell for Great Good is an excellent resource.

For even more fun, if you have a function with more than one argument, you can still use the lovely Applicative style.

insult :: String -> String -> String
insult a b = a ++ ", you're almost as ugly as " ++ b

Try

insult "Fred" "Barney"
insult "Fred" $ "Barney"
insult <$> ["Fred","Barney"] <*> ["Wilma","Betty"]
insult <$> Just "Fred" <*> Nothing
insult <$> Just "Fred" <*> Just "Wilma"
insult <$> readFile "someone.txt" <*> readFile "someoneElse.txt"

Here you use <$> after the function and <*> between the arguments it needs. How it works is a little mind-blowing at first, but it's the most functional style of writing effectful computations.

Next read up about Applicative Functors. They're great.
http://learnyouahaskell.com/functors-applicative-functors-and-monoids
http://en.wikibooks.org/wiki/Haskell/Applicative_Functors

like image 142
AndrewC Avatar answered Dec 05 '22 04:12

AndrewC


import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Either

readAndChange i o = do
    result <- fmap (either id id) $ runEitherT $ forever $ do
        iseof <- lift $ hIsEof i
        when iseof $ left o -- Break and return 'o'
        line <- lift $ hGetLine i
        lift $ hPutStrLn o $ show $ extractNameAndId line
    -- 'result' now contains the value of 'o' you ended on
    doSomeWithingWith result

To understand why this technique works, read this.

like image 43
Gabriella Gonzalez Avatar answered Dec 05 '22 05:12

Gabriella Gonzalez