Reading Files in Elixir

Here’s the translation of the Go code example for reading files into Elixir, with explanations in Markdown format suitable for Hugo:

Reading and writing files are basic tasks needed for many Elixir programs. First we’ll look at some examples of reading files.

# Reading files requires checking most calls for errors.
# This helper will streamline our error checks below.
defmodule FileReader do
  def check({:ok, result}), do: result
  def check({:error, reason}), do: raise("Error: #{reason}")

  def main do
    # Perhaps the most basic file reading task is
    # slurping a file's entire contents into memory.
    dat = check(File.read("/tmp/dat"))
    IO.write(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 File handle.
    {:ok, file} = File.open("/tmp/dat", [:read])

    # Read some bytes from the beginning of the file.
    # Allow up to 5 to be read but also note how many
    # actually were read.
    b1 = IO.binread(file, 5)
    IO.puts("#{byte_size(b1)} bytes: #{b1}")

    # You can also seek to a known location in the file
    # and read from there.
    :file.position(file, 6)
    b2 = IO.binread(file, 2)
    IO.puts("#{byte_size(b2)} bytes @ 6: #{b2}")

    # The :file module provides functions for seeking
    # relative to the current position and end of file.
    :file.position(file, {:cur, 4})
    :file.position(file, {:eof, -10})

    # The File module provides some functions that may
    # be helpful for file reading. For example, reads
    # like the ones above can be more robustly
    # implemented with read!/3.
    :file.position(file, 6)
    b3 = File.read!(file, 2)
    IO.puts("#{byte_size(b3)} bytes @ 6: #{b3}")

    # There is no built-in rewind, but
    # :file.position(file, :bof) accomplishes this.
    :file.position(file, :bof)

    # The IO module implements functions for efficient
    # reading with many small reads and additional
    # reading methods.
    b4 = IO.binread(file, 5)
    IO.puts("5 bytes: #{b4}")

    # Close the file when you're done (usually this would
    # be scheduled immediately after opening with
    # try/rescue/after).
    File.close(file)
  end
end

FileReader.main()

To run this program:

$ echo "hello" > /tmp/dat
$ echo "elixir" >> /tmp/dat
$ elixir reading_files.exs
hello
elixir
5 bytes: hello
2 bytes @ 6: el
2 bytes @ 6: el
5 bytes: hello

In this Elixir version:

  1. We use the File module for basic file operations and the :file Erlang module for more advanced operations like seeking.
  2. Error handling is done through pattern matching in the check/1 function.
  3. We use IO.binread/2 for reading specific amounts of data from the file.
  4. Seeking is done with :file.position/2, which is similar to Seek in the Go version.
  5. We use File.read!/3 as an alternative to io.ReadAtLeast in the Go version.
  6. There’s no direct equivalent to bufio.NewReader, but IO.binread/2 provides efficient reading capabilities.

Next, we’ll look at writing files.