1

It's me again:). I try to write a program which copies lines which number is divisible by 5 to another file. Here is code (sorry for Polish names):

import IO

przepiszConHelper :: Handle -> Handle -> Integer -> Integer -> IO ()
przepiszConHelper wejscie wyjscie liczba licznik = do
    eof <- hIsEOF wejscie
    if eof then return ()
        else
            linia <- hGetLine wejscie
            if (mod licznik liczba) == 0 then
                hPutStrLn wyjscie linia
            przepiszConHelper wejscie wyjscie liczba (licznik + 1)

przepiszCon :: String -> String -> Integer -> IO ()
przepiszCon wejscie wyjscie liczba = do
    wej <- openFile wejscie ReadMode
    wyj <- openFile wyjscie WriteMode
    przepiszConHelper wej wyj liczba 0
    hClose wej
    hClose wyj

main = przepiszCoN "wejscie.txt" "wyjscie.txt" 5

I think it should works... but I get one, strange error:

przepisz.hs:6:9:
    Parse error in pattern: if eof then return () else linia

Which makes no sense for me. I had been using the same expression in another programs and it worked like a harm. I tried to remove these lines and write them with different indentations (I remember that I had some issues with white-spaces before). But I still get the same error:(.


--edit

OK, I have first error... it's just else do instead of else. But here comes another problem:

przepisz.hs:11:25: parse error on input `przepiszConHelper'
Sjoerd Visscher
  • 11,840
  • 2
  • 47
  • 59
ciembor
  • 7,189
  • 13
  • 59
  • 100

2 Answers2

3

The problem is here:

if eof then return ()
    else
        linia <- hGetLine wejscie

Indeed, the problem is not whitespace--your issue is that you seem to expect a do block to extend inside subexpressions, which is not the case. The else clause needs to define its own do block:

if eof then return ()
    else do
        linia <- hGetLine wejscie

There's another error after that, however:

if (mod licznik liczba) == 0 then
    hPutStrLn wyjscie linia
przepiszConHelper wejscie wyjscie liczba (licznik + 1)

You're missing an else clause for this if. if is always an expression in Haskell, so it must always evaluate to something. If you want to express "do this IO action if the condition is true, otherwise do nothing" you can use return ():

if (mod licznik liczba) == 0 
    then hPutStrLn wyjscie linia
    else return ()

The standard library even includes the function when to express this idea:

when (mod licznik liczba == 0) $ hPutStrLn wyjscie linia

If you wanted, you could rewrite the outer if expression the same way, and get something like this:

przepiszConHelper :: Handle -> Handle -> Integer -> Integer -> IO ()
przepiszConHelper wejscie wyjscie liczba licznik = do
    eof <- hIsEOF wejscie
    when (not eof) $ do 
        linia <- hGetLine wejscie
        when (mod licznik liczba == 0) $ hPutStrLn wyjscie linia
        przepiszConHelper wejscie wyjscie liczba (licznik + 1)
C. A. McCann
  • 76,893
  • 19
  • 209
  • 302
  • 1
    FWIW, the location of `when` in the standard library is in the `Control.Monad` module: `import Control.Monad (when)` – luqui Jan 13 '13 at 10:02
  • @luqui: Oh, bother. It's not in the Prelude? My `.ghci` file has once again led me astray. – C. A. McCann Jan 13 '13 at 18:25
3

I'd like to suggest a different way of doing things, mainly to separate pure code from IO and use more standard functions. Also, I often try to write smaller functions than you have done, keeping each one simple and easy to maintain.

First let's keep the nth element of a list. We'll do this by zipping it with the numbers [1..] and then only keeping those where the number is divisible by n.

przechowac :: Int -> [a] -> [a]
przechowac n listy = [a| (a,i) <- zip listy [1..], i `mod` n == 0]

So for example,

*Main> przechowac 3 [1..10]
[3,6,9]

Next let's stay pure whilst using that on the lines of a String:

przechowacLinie :: Int -> String -> String
przechowacLinie n = unlines . przechowac n . lines

*Main> putStrLn "Hej,\nHaskell\njest\nfantastyczny"
Hej,
Haskell
jest
fantastyczny
*Main> putStrLn (przechowacLinie 2 "Hej,\nHaskell\njest\nfantastyczny")
Haskell
fantastyczny

Now let's wrap it in some IO. We'll make a function that applies a function to a file, saving it to an output file:

zastosowacFunkcje :: (String -> String) -> FilePath -> FilePath -> IO ()
zastosowacFunkcje f wejscie wyjscie = do
   wej <- readFile wejscie
   writeFile wyjscie (f wej)

(FilePath is a type synonym of String that makes type signatures clearer.)

I can use that to write your function:

przepiszCon :: Int -> FilePath -> FilePath -> IO ()
przepiszCon liczba = zastosowacFunkcje (przechowacLinie liczba)

In actual fact, these days I've stopped using functions like zastosowacFunkcje and now I'd tend to just do

przepiszCon :: Int -> FilePath -> FilePath -> IO ()
przepiszCon n wejscie wyjscie = fmap (przechowacLinie n) (readFile wejscie)
                                                     >>= writeFile wyjscie

because I find fmap so handy.

Community
  • 1
  • 1
AndrewC
  • 32,300
  • 7
  • 79
  • 115