CSCI 2041

ADVANCED PROGRAMMING PRINCIPLES

I/O in Ocaml

Channels

In Ocaml, I/O operations are performed on “channels” that we can think of as files.

There are two types of channels:

  • type in_channel can be used to read input into a program
  • type out_channel that can be used for output.

There are also three “pre-opened channels”:

  • val stdin : in_channel (the standard input)
  • val stdout : out_channel (the standard output)
  • val stderr : out_channel (for logging/printing errors)

The common way to open a channel is by file name:

  • val open_out : string -> out_channel takes a filename as input and returns an out_channel
  • val open_in : string -> in_channel takes a filename as input and returns an in_channel

These can raise Sys_error if the file cannot be opened.

Once a program has finished using a channel, it should be closed with:

  • val close_out : out_channel -> unit or
  • val close_in : in_channel -> unit

Reading data

To read from a file, the most common methods are

  • val input_char : in_channel -> char - reads a single char
  • val input_line : in_channel -> string - reads a line, trims newline.
  • val read_line : unit -> string (input_line from stdin)

If the channel (file) does not have more data, these raise End_of_file.

A common pattern is the “stream” approach:

  • Given a filename, return a function of type unit -> string option
  • When a line is available, the function returns Some v
  • When End_of_file is raised, close the channel and return None
let stream_of_file f =
  let ic = open_in f in
  let nextline () =
    begin
      try Some (input_line ic) with
      End_of_file -> let () = close_in ic in None
    end
  in nextline

Another approach makes a list of all lines in the file:

(* "Not wrong"? *)
let get_file_lines f =
  let rec gfl_helper ic =
    try let l = (input_line ic) in l::(gfl_helper ic) with End_of_file -> []
  in gfl_helper (open_in f)  

Still not quite right:

let get_file_lines f =
  let ic = open_in f in
  let rec gfl_helper acc =
    try gfl_helper ((input_line ic)::acc)
    with End_of_file -> (let () = close_in ic in acc)
  in List.rev (gfl_helper [])

try should wrap only the input_line call:

let get_file_lines f =
  let ic = open_in f in
  let rec make_list acc = (* the "really" tail-recursive way *)
    match (try Some (input_line ic) with End_of_file -> None) with
    | None -> let () = close_in ic in List.rev acc
    | Some l -> make_list l::acc
  in make_list []

Writing data

To write to an out_channel, the commonly used functions are:

  • val output_char : out_channel -> char -> unit
  • val output_string : out_channel -> string -> unit

These can raise Sys_error if writing fails.

For writing to stdout there are also:

val print_endline : string -> unit
val print_string : string -> unit
val print_char : char -> unit
val print_float : float -> unit
val print_int : int -> unit
val print_newline : unit -> unit

Example: write a string list to a file, one line at a time:

let write_list lst fname =
  let oc = open_out fname in
  let () = List.iter (fun l -> output_string oc (l^"\n")) lst in
  close_out oc

What about writing a list of int*int pairs, separated by tabs, followed by newlines?

let write_pair_list lst fname =
  let oc = open_out fname in
  let format_pair m n = (string_of_int m) ^ "\t" ^ (string_of_int n) ^ "\n" in
  let () = List.iter (fun (m,n) -> output_string oc (format_pair m n)) lst in
  close_out oc

Printf

The Printf module provides a mechanism for formatted output:

  • Printf.sprintf writes formatted input to a string
  • Printf.printf prints formatted input to stdout
  • Printf.fprintf oc prints formatted input to oc : out_channel

Printf.printf <fs> is a function with the correct number of inputs to match the format string <fs>:

Printf.printf "length(%s)=%d\n" : string -> int -> unit
Printf.printf "launch angle: %5.2f\n" : float -> unit
Printf.printf "The predicate is %b\n" : bool -> unit

More on conversions in Hickey and the Ocaml manual

Example: write_pair_list again…

let write_pair_list lst fname =
  let oc = open_out fname in
  let () = List.iter (fun (m,n) -> Printf.fprintf oc "%d\t%d\n" m n) lst in
  close_out oc

cs2041.org

// reveal.js plugins