5

I need to modify a file in-place. So I planned to read file contents, process them, then write the output to the same file:

main = do
  input <- readFile "file.txt"
  let output = (map toUpper input) 
  -- putStrLn $ show $ length output
  writeFile "file.txt" output

But the problem is, it works as expected only if I uncomment the 4th line - where I just output number of characters to console. If I don't uncomment it, I get

openFile: resource busy (file is locked)

Is there a way to force reading of that file?

Mechanical snail
  • 29,755
  • 14
  • 88
  • 113
Rogach
  • 26,050
  • 21
  • 93
  • 172
  • 1
    A standard expedient is to use the length, as your commented line did; thus you can write `length output `seq` writeFile "file.txt" output` See for example the [`strict` package](http://hackage.haskell.org/packages/archive/strict/0.3.2/doc/html/src/System-IO-Strict.html) – applicative Oct 27 '12 at 05:44

1 Answers1

5

The simplest thing might be strict ByteString IO:

import qualified Data.ByteString.Char8 as B

main = do
  input <- B.readFile "file.txt"
  B.writeFile "file.txt" $ B.map toUpper input

As you can see, it's the same code -- but with some functions replaced with ByteString versions.

Lazy IO

The problem that you're running into is that some of Haskell's IO functions use "Lazy IO", which has surprising semantics. In almost every program I would avoid lazy IO.

These days, people are looking for replacements to Lazy IO like Conduit and the like, and lazy IO is seen as an ugly hack which unfortunately is stuck in the standard library.

Dietrich Epp
  • 205,541
  • 37
  • 345
  • 415
  • Well, but what if I need to do some processing on those strings, that I can't do on ByteString? – Rogach Oct 27 '12 at 05:36
  • @Rogach: You can do anything with a `ByteString` except for the bizarre stuff like infinite strings. – Dietrich Epp Oct 27 '12 at 05:42
  • I suspect so, but since these I my first steps using Haskell, I have some trouble. At the very least, I have several functions that work with String, and they don't seem to like ByteString. – Rogach Oct 27 '12 at 05:44
  • Just call `Data.ByteString.UTF8.fromString` and `toString`. – Dietrich Epp Oct 27 '12 at 05:48
  • 2
    Rogach, how about `input <- fmap Data.Text.unpack $ Data.Text.IO.readFile "file.txt"` Here `input` is a lazily generated string, but it is coming from a completely evaluated Text value, so the rest of the block can continue as above. This is maybe slightly more humane than using bytestring... – applicative Oct 27 '12 at 05:50
  • @applicative - Will it be better than that `System.IO.Strict(readFile)`? – Rogach Oct 27 '12 at 05:52
  • It will depend what you are doing, but one advantage is that the completely evaluated string will occupy much more space in memory than the `Text` will. I'm not sure there would be a difference in the case of your program, but in some it might not be necessary to completely evaluate the string. You would be using lazy string operations, but outside of 'lazy io', which mostly poses problems with reading. – applicative Oct 27 '12 at 06:03
  • @applicative: Why is it more humane? The `Text` version will convert UTF-8 -> UTF-16 -> UTF-32, whereas the `ByteString` version does UTF-8 -> UTF-32. – Dietrich Epp Oct 27 '12 at 06:04
  • I was just thinking that the `text` package is easier to use, but maybe I'm wrong? – applicative Oct 27 '12 at 06:06
  • @applicative: It is easier to use, but if you're converting to `String` anyway there's no benefit. – Dietrich Epp Oct 27 '12 at 06:06
  • @DietrichEpp - As far as I understand, the difference is just in the way I read the file into string - either with ByteString or Text, I still strictly get String. Am I right? – Rogach Oct 27 '12 at 06:09
  • @Rogach: Yes, the end result is you get the same string either way. (The string might never actually exist in memory if it is optimized out, but it is the same conceptual string.) – Dietrich Epp Oct 27 '12 at 06:11
  • I agree there's no benefit if youre converting to String only, but was thinking it might incline Rogach to get the hang of Text. – applicative Oct 27 '12 at 06:11
  • Agreed. `Text` is a fantastic package that shows the very best in Haskell API design, and has a very high-performance implementation. – Dietrich Epp Oct 27 '12 at 06:14
  • @applicative - In my case, memory really doesn't matter - I'm transforming files of size ~30kb. I'm trying to get hang of text - that stuff with `fmap` is just mapping over value inside IO monad? – Rogach Oct 27 '12 at 06:16
  • @applicative - And that Data.Text is not in stdlib? – Rogach Oct 27 '12 at 06:17
  • @Rogach: That's right, `Data.Text` isn't in the standard library. But you have Cabal, right? Run `cabal install text` if you want to use the library. – Dietrich Epp Oct 27 '12 at 15:43