Writing Files in F#

Here’s the F# translation of the Go code example for writing files:

open System
open System.IO

let check (e : Exception option) =
    match e with
    | Some ex -> raise ex
    | None -> ()

[<EntryPoint>]
let main argv =
    // To start, here's how to dump a string (or just bytes) into a file.
    let d1 = "hello\nF#\n"
    File.WriteAllText("/tmp/dat1", d1)

    // For more granular writes, open a file for writing.
    use f = new StreamWriter("/tmp/dat2")

    // You can Write byte arrays as you'd expect.
    let d2 = [|115uy; 111uy; 109uy; 101uy; 10uy|]
    f.BaseStream.Write(d2, 0, d2.Length)
    printfn "wrote %d bytes" d2.Length

    // A WriteString is also available.
    let n3 = f.Write("writes\n")
    printfn "wrote %d bytes" n3

    // Issue a Flush to ensure writes are committed to stable storage.
    f.Flush()

    // StreamWriter provides buffered writers in addition to the buffered readers we saw earlier.
    let w = new StreamWriter(f.BaseStream)
    let n4 = w.Write("buffered\n")
    printfn "wrote %d bytes" n4

    // Use Flush to ensure all buffered operations have been applied to the underlying writer.
    w.Flush()

    0 // return an integer exit code

This F# code demonstrates file writing operations similar to the original example. Here’s a breakdown of the changes and explanations:

  1. We use System.IO namespace for file operations in F#.

  2. The check function is implemented using pattern matching on Exception option.

  3. Instead of os.WriteFile, we use File.WriteAllText to write a string directly to a file.

  4. We use StreamWriter for more granular file writing operations.

  5. The defer keyword doesn’t exist in F#. Instead, we use the use keyword, which ensures that the StreamWriter is properly disposed of when it goes out of scope.

  6. Byte arrays in F# are created using the syntax [|...|] with the uy suffix for byte literals.

  7. We write to the underlying stream directly using f.BaseStream.Write for byte arrays.

  8. The WriteString method doesn’t exist in F#’s StreamWriter, so we use the Write method instead.

  9. Sync is replaced with Flush in F#.

  10. For buffered writing, we create a new StreamWriter on top of the existing stream.

To run the F# file-writing code:

$ dotnet fsi writing-files.fs
wrote 5 bytes
wrote 6 bytes
wrote 9 bytes

Then check the contents of the written files:

$ cat /tmp/dat1
hello
F#
$ cat /tmp/dat2
some
writes
buffered

Next, we’ll look at applying some of the file I/O ideas we’ve just seen to the stdin and stdout streams.