4

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
Aslan986
  • 8,794
  • 10
  • 41
  • 69

5 Answers5

13

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

BenMorel
  • 30,280
  • 40
  • 163
  • 285
AndrewC
  • 31,331
  • 7
  • 73
  • 113
3
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.

Gabriel Gonzalez
  • 34,065
  • 3
  • 72
  • 132
3

You can do recursion by using let pattern, which is however similar to defining recursion function separately :

main = do 
    let x = 10 
    let loop = do 
        print 1 
        when (x<20) loop 
    loop

You can use fix from Control.Monad.Fix also to achieve similar behaviour

main = do 
    let x = 10 
    fix $ \loop -> do 
        print 1 
        when (x<20) loop

What you are stating is kind of goto label pattern. I don't know wheter you can achieve that kind of behaviour, but above use of fix or let can easily help you achieve recursion.

[edit] There are some more patterns to achieve similar results like using the Cont monad as in

getCC' :: MonadCont m => a -> m (a, a -> m b)
getCC' x0 = callCC (\c -> let f x = c (x, f) in return (x0, f))

main = (`runContT` return) $ do 
    (x, loopBack) <- getCC' 0
    lift (print x)
    when (x < 10) (loopBack (x + 1))
    lift (putStrLn "finish")

which will print numbers from 1 to 10. See goto using continuation for better explanation.

There is also a Goto monad and transformer. I have not used it. You might find it suitable for your need.

Community
  • 1
  • 1
Satvik
  • 11,140
  • 1
  • 35
  • 45
  • 1
    I personally quite like the `fix` approach. Note that it's morally equivalent to the "recusive `let`" approach, since `fix` is one way to define recursion. (In fact, I just switched some of my code to use `fix` instead of a recursive `let`.) Also, note that `fix` is also available in `Data.Function`; there's nothing monadic about it. `Control.Monad.Fix` *also* provides the `MonadFix` type class, which provides `mfix :: MonadFix m => (a -> m a) -> m a`. – Antal Spector-Zabusky Sep 11 '12 at 20:09
  • 1
    For anyone who likes the "looping combinator" approach, you can find more sophisticated loops than just `fix` in [the `monad-loops` package](http://hackage.haskell.org/package/monad-loops). It's pretty neat. – C. A. McCann Sep 11 '12 at 20:34
2

The first thing you should do is read the documentation for the Control.Monad module, which is an absolute essential for writing Haskell code. While you're at it, install the Control.Monad.Loops package from Hackage, and read the docs on that; you might be particularly interested in the whileM_ function there:

import Data.Functor ((<$>)
import Control.Monad.Loops (whileM_)

readAndChange i o = 
    whileM_ notEOF $ do line <- hGetLine i
                        hPutStrLn o (show (extractNameAndId line))
        where notEOF = not <$> (hIsEOF i)

The library in question implements whileM_ like this, which is the pattern you're looking for:

-- |Execute an action repeatedly as long as the given boolean expression
-- returns True.  The condition is evaluated before the loop body.
-- Discards results.
whileM_ :: (Monad m) => m Bool -> m a -> m ()
whileM_ p f = do
        x <- p
        if x
                then do
                        f
                        whileM_ p f
                else return ()

Still, I have to concur that you're writing this in an excessively imperative manner. Try to think of it this way: your program is basically transforming an input string into an output string. This immediately suggests that the core of your program's logic should have this type:

transformInput :: String -> String
transformInput = ...

Your transformation proceeds in a line-by-line basis. This means you can refine the sketch this way (the lines function splits a string into lines; unlines rejoins the list):

transformInput :: String -> String
transformInput input = unlines (map transformLine (lines input))

transformLine :: String -> String
transformLine line = show (extractNameAndId line)

Now you've got the core of the logic in the transformInput function, so you just need to somehow hook that up to the input and output handles. If you were dealing with stdin and stdout, you could use the interact function to do that. But we can actually steal its implementation and modify it:

hInteract       ::  Handle -> Handle -> (String -> String) -> IO ()
hInteract i o f =   do s <- hGetContents i
                       hPutStr o (f s)

And now, voilà:

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

Warning: all code untested.


One last thing, in the interest of full disclosure: the trick here is that hGetContents performs lazy I/O: it basically allows you to treat the whole contents of a handle as a String, and thus apply the transformInput function to the contents of the handle. But it's all done lazily, so it doesn't actually have to read the whole file at once.

This is the easiest way to do it, and you should learn it, but it has a big weakness, which is that you can lose some control over when the handles are closed. For quick and dirty programs this is ok, however.

Luis Casillas
  • 28,476
  • 5
  • 46
  • 97
0

Unlike imperative programming languages, and also unlike other functional programming languages, Haskell includes no syntactic construct for writing for-loops or while-loops, which is what you seem to asking for here.

The idea is that recursive processes and iterative processes can uniformly be captured by recursive functions. It's just that iterative processes are captured as particular kinds of recursive functions: these functions are tail recursive. Imperative code such as that which appears inside do-blocks is no exception. You might find this lack explicit looping construct annoying because you have to define a new function for every loop, hence in a sense having to name the loop. However, this is a trivial price to pay for the uniformity and simplicity of the Haskell approach, for three reasons:

  1. You don't have to define the function that represents a loop at top-level. You can define it locally.

  2. In Haskell many people usually always use the same names for these kinds of loops. Popular choices here are go and aux. Your code could thus be rewritten as follows:

    toCouplesFile = do inputFile <- openFile "deletedId.csv" ReadMode
                       outputFile <- openFile "couples.txt" WriteMode
                       let go = do
                            iseof <- hIsEOF inputFile
                            if iseof then (return outputFile)
                            else do line <- hGetLine inputFile
                                    hPutStrLn outputFile (show (extractNameAndId line))
                                    go
                       go
    
  3. Finally, the absence of looping constructs is pretty immaterial because often times we don't need to write loops at all. In your case, other answers in this thread have shown many ways of doing this.

macron
  • 1,768
  • 12
  • 14