Reading Files in F#

Our first example demonstrates reading files in F#. Reading and writing files are basic tasks needed for many F# programs. Let’s look at some examples of reading files.

open System
open System.IO

// Reading files requires checking most operations for errors.
// This helper will streamline our error checks below.
let check (result: Result<'T, exn>) =
    match result with
    | Ok value -> value
    | Error e -> raise e

[<EntryPoint>]
let main argv =
    // Perhaps the most basic file reading task is
    // slurping a file's entire contents into memory.
    let dat = 
        File.ReadAllText("/tmp/dat")
        |> check
    printfn "%s" dat

    // You'll often want more control over how and what
    // parts of a file are read. For these tasks, start
    // by opening a file to obtain a FileStream.
    use f = new FileStream("/tmp/dat", FileMode.Open)

    // Read some bytes from the beginning of the file.
    // Allow up to 5 to be read but also note how many
    // actually were read.
    let b1 = Array.zeroCreate 5
    let n1 = f.Read(b1, 0, b1.Length)
    printfn "%d bytes: %s" n1 (System.Text.Encoding.UTF8.GetString(b1, 0, n1))

    // You can also seek to a known location in the file
    // and read from there.
    f.Seek(6L, SeekOrigin.Begin) |> ignore
    let b2 = Array.zeroCreate 2
    let n2 = f.Read(b2, 0, b2.Length)
    printfn "%d bytes @ %d: %s" n2 6 (System.Text.Encoding.UTF8.GetString(b2, 0, n2))

    // Other methods of seeking are relative to the
    // current cursor position,
    f.Seek(4L, SeekOrigin.Current) |> ignore

    // and relative to the end of the file.
    f.Seek(-10L, SeekOrigin.End) |> ignore

    // The FileStream provides some functions that may
    // be helpful for file reading. For example, reads
    // like the ones above can be more robustly
    // implemented with ReadExactly.
    f.Seek(6L, SeekOrigin.Begin) |> ignore
    let b3 = Array.zeroCreate 2
    f.ReadExactly(b3, 0, b3.Length)
    printfn "%d bytes @ %d: %s" b3.Length 6 (System.Text.Encoding.UTF8.GetString(b3))

    // There is no built-in rewind, but
    // Seek(0, SeekOrigin.Begin) accomplishes this.
    f.Seek(0L, SeekOrigin.Begin) |> ignore

    // The StreamReader class implements a buffered
    // reader that may be useful both for its efficiency
    // with many small reads and because of the additional
    // reading methods it provides.
    use r4 = new StreamReader(f)
    let b4 = r4.ReadLine()
    printfn "5 bytes: %s" (b4.Substring(0, 5))

    // The 'use' keyword ensures that the file is closed
    // when we're done (it's equivalent to 'using' in C#)

    0 // return an integer exit code

To run the program:

$ echo "hello" > /tmp/dat
$ echo "f#" >>   /tmp/dat
$ dotnet run
hello
f#
5 bytes: hello
2 bytes @ 6: f#
2 bytes @ 6: f#
5 bytes: hello

Next, we’ll look at writing files.