Ocaml/FAQ/References and Records

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск

Содержание

[править] 11. References and Records

[править] Introduction

(* Create a reference to an integer *)
let aref = ref 0
 
let () =
  (* Assign to aref's contents *)
  aref := 3;
 
  (* Print the value that the reference "aref" refers to *)
  Printf.printf "%d\n" !aref;
 
  (* Since references are just records with a single field, "contents",
     the following operations have the same effect as above *)
  aref.contents <- 3;
  Printf.printf "%d\n" aref.contents;
 
  (* Fast increment and decrement operations are available for int refs *)
  incr aref; Printf.printf "after incr: %d\n" !aref;
  decr aref; Printf.printf "after decr: %d\n" !aref
 
(* Create a type for "person" records *)
type person = { name : string;
                address : string;
                birthday : int;
              }
 
let () =
  (* Create a "person" record *)
  let nat = { name     = "Leonhard Euler";
              address  = "1729 Ramunjan Lane\nMathword, PI 31416";
              birthday = 0x5bb5580;
            } in
 
  (* Display the person's name and address *)
  Printf.printf "\nname: %s\naddress: %s\n" nat.name nat.address;
 
  (* Same as above, using pattern-matching *)
  let {name=n; address=a} = nat in
  Printf.printf "\nname: %s\naddress: %s\n" n a

[править] Taking References to Arrays

(* The following two sections use lists instead of arrays since
   list refs can be enlarged and copied easily. Also, arrays are
   mutable in OCaml, whereas lists are immutable. *)
 
(* Create a reference to a list *)
let lref      = ref list
let anon_list = ref [9; 7; 5; 3; 1]
let anon_copy = ref !anon_list
 
let () =
  (* Add an item to the list *)
  anon_list := 11 :: !anon_list;
 
  (* Get the number of items from the list ref *)
  let num_items = List.length !anon_list in
 
  (* Print original data *)
  print_endline (String.concat ", "
                   (List.map (fun i -> string_of_int i) !anon_list));
 
  (* Sort it *)
  anon_list := List.sort compare !anon_list;
 
  (* Print sorted data *)
  print_endline (String.concat ", "
                   (List.map (fun i -> string_of_int i) !anon_list));

[править] Making Hashes of Arrays

(* Create a hash that maps strings to string lists *)
let (hash : (string, string list) Hashtbl.t) = Hashtbl.create 0
 
(* Define a function to add a string to the string list associated
   with a key in the hash creating the string list if necessary *)
let add hash key value =
  Hashtbl.replace hash key
    (try value :: Hashtbl.find hash key
     with Not_found -> [value])
 
let () =
  (* Populate the hash with some data *)
  add hash "fruit" "apple";
  add hash "fruit" "banana";
  add hash "wine" "merlot";
  add hash "cheese" "cheddar";
  add hash "cheese" "brie";
  add hash "cheese" "havarti";
 
  (* Iterate and print out the hash's contents *)
  Hashtbl.iter
    (fun key values ->
       Printf.printf "%s: %s\n" key
         (String.concat ", " values))
    hash
 
(* Hashtbl is somewhat unusual in that it allows multiple values for
   a given key. By using Hashtbl.add instead of Hashtbl.replace, and
   using strings as values instead of string lists, we can save some
   memory *)
let (hash : (string, string) Hashtbl.t) = Hashtbl.create 0
let () =
  Hashtbl.add hash "foo" "bar";
  Hashtbl.add hash "foo" "baz";
  Hashtbl.add hash "goo" "arc";
  Hashtbl.iter (Printf.printf "%s => %s\n") hash

[править] Taking References to Hashes

(* Hashtbls are mutable, so creating a reference to a hash is usually
   not necessary; it creates an *additional* level of indirection. *)
let href = ref hash
let anon_hash = ref (Hashtbl.create 0)
let () =
  (* Have some fun with locally-defined operators *)
  let ( => ) = Hashtbl.replace !anon_hash in
  ( "key1" => "value1"; "key2" => "value2" )
let anon_hash_copy = ref (Hashtbl.copy !href)

[править] Taking References to Functions

(* Create a reference to a function *)
let fref = ref func
let fref = ref (fun () -> (* ... *) ())
 
(* Call the referent function *)
let () = !fref ()
 
(* Create a reference to an association list with function values. *)
let commands = ref []
let () =
  let ( => ) name func = commands := (name, func) :: !commands in
  (
    "happy" => joy;
    "sad"   => sullen;
    "done"  => (fun () -> print_endline "See ya!"; exit 0);
    "mad"   => angry;
  )
 
let () =
  while true do
    print_string "How are you? ";
    let string = read_line () in
    try
      let command = List.assoc string !commands in
      command ()
    with Not_found ->
      Printf.printf "No such command: %s\n" string
  done
 
(* Use closures to generate functions that count. *)
 
let counter_maker () =
  let start = ref 0 in
  fun () ->                             (* this is a closure *)
    let result = !start in              (* lexical from enclosing scope *)
    incr start; result
 
let counter1 = counter_maker ()
let counter2 = counter_maker ()
 
let () =
  for i = 0 to 4 do
    Printf.printf "%d\n" (counter1 ())
  done;
  Printf.printf "%d %d\n" (counter1 ()) (counter2 ())
  (*
    0
    1
    2
    3
    4
    5 0
  *)
 
(* Use closures to generate functions that keep track of time.
   Note that this example does not need references, since
   since functions are just ordinary values in OCaml. *)
 
#load "unix.cma";;
 
let timestamp () =
  let start_time = Unix.time () in
  fun () -> int_of_float (Unix.time () -. start_time)
 
let () =
  let early = timestamp () in
  Unix.sleep 20;
  let later = timestamp () in
  Unix.sleep 10;
  Printf.printf "It's been %d seconds since early.\n" (early ());
  Printf.printf "It's been %d seconds since later.\n" (later ());
  (*
    It's been 30 seconds since early.
    It's been 10 seconds since later.
  *)

[править] Taking References to Scalars

(* Environments are immutable in OCaml; there is no way to get a
   reference to a value. If you need a mutable cell, use "ref" as
   described in the introduction. If you need to refer to values
   by name strings, use a Hashtbl.t or similar data structure. *)

[править] Creating Arrays of Scalar References

(* Create a couple of integer references *)
let a = ref 0
let b = ref 0
 
(* Create an array of the references *)
let array_of_refs = [| a; b |]
 
let () =
  (* Set the value of an element *)
  array_of_refs.(1) := 12;              (* b := 12 *)
 
  (* Note that this is *not* the same as array mutation! If we were to do:
       array_of_refs.(1) <- ref 12
     (or drop the refs altogether) then we would no longer be aliasing "b".
  *)
 
  (* Get the value of an element *)
  Printf.printf "%d %d\n" !(array_of_refs.(1)) !b
 
let () =
  let (a, b, c, d) = (ref 1, ref 2, ref 3, ref 4) in (* initialize *)
  let array = [| a; b; c; d |] in                    (* refs to each value *)
 
  array.(2) := !(array.(2)) + 9;        (* !c is now 12 *)
 
  let tmp = array.(Array.length array - 1) in
  tmp := !tmp * 5;                      (* !d is now 20 *)

[править] Using Closures Instead of Objects

(* Since record field names must be unique to their enclosing module,
   define a module to encapsulate the fields of the record type that
   will contain the "methods". *)
module Counter = struct
  type t = { next  : unit -> int;
             prev  : unit -> int;
             last  : unit -> int;
             get   : unit -> int;
             set   : int  -> unit;
             bump  : int  -> unit;
             reset : unit -> int }
 
  let make count =
    let start = count in
    let count = ref start in
    let prev () = decr count; !count in
    { next  = (fun () -> incr count; !count);
      prev  = prev; last = prev;
      get   = (fun () -> !count);
      set   = (fun count' -> count := count');
      bump  = (fun count' -> count := !count + count');
      reset = (fun () -> count := start; !count)
    }
end
 
(* Create and use a couple of counters. *)
let () =
  let c1 = Counter.make 20 in
  let c2 = Counter.make 77 in
 
  Printf.printf "next c1: %d\n" (c1.Counter.next ()); (* 21 *)
  Printf.printf "next c2: %d\n" (c2.Counter.next ()); (* 78 *)
  Printf.printf "next c1: %d\n" (c1.Counter.next ()); (* 22 *)
  Printf.printf "last c1: %d\n" (c1.Counter.prev ()); (* 21 *)
  Printf.printf "old  c2: %d\n" (c2.Counter.reset ()) (* 77 *)
 
(* Same as above, but using a "local open" to temporarily expose
   the record fields for convenience. *)
let () =
  let c1 = Counter.make 20 in
  let c2 = Counter.make 77 in
  let module Local = struct
    open Counter
    let () =
      Printf.printf "next c1: %d\n" (c1.next ()); (* 21 *)
      Printf.printf "next c2: %d\n" (c2.next ()); (* 78 *)
      Printf.printf "next c1: %d\n" (c1.next ()); (* 22 *)
      Printf.printf "last c1: %d\n" (c1.prev ()); (* 21 *)
      Printf.printf "old  c2: %d\n" (c2.reset ()) (* 77 *)
  end in ()

[править] Creating References to Methods

(* There is no need to use references just to have a function that
   calls a method. Either write a lambda: *)
 
let mref = fun x y z -> obj#meth x y z
 
(* Or, just refer to the method directly: *)
 
let mref = obj#meth
 
(* Later... *)
 
let () = mref "args" "go" "here"

[править] Constructing Records

#load "str.cma";;
 
type record = { name : string;
                empno : int;
                mutable title : string;
                mutable age : int;
                mutable salary : float;
                mutable pals : string list;
              }
 
let record = { name = "Jason";
               empno = 132;
               title = "deputy peon";
               age = 23;
               salary = 37000.00;
               pals = [ "Norbert"; "Rhys"; "Phineas" ]
             }
 
let () =
  Printf.printf "I am %s, and my pals are %s.\n"
    record.name
    (String.concat ", " record.pals)
 
let byname = Hashtbl.create 0
 
let () =
  (* store record *)
  Hashtbl.replace byname record.name record;
 
  (* later on, look up by name *)
  begin
    try
      let rp = Hashtbl.find byname "Aron" in
      Printf.printf "Aron is employee %d\n" rp.empno
    with Not_found ->
      (* raised if missing *)
      ()
  end;
 
  (* give jason a new pal *)
  let jason = Hashtbl.find byname "Jason" in
  jason.pals <- "Theodore" :: jason.pals;
  Printf.printf "Jason now has %d pals\n" (List.length jason.pals);
 
  Hashtbl.iter
    (fun name record ->
       Printf.printf "%s is employee number %d\n" name record.empno)
    byname
 
let employees = Hashtbl.create 0
 
let () =
  (* store record *)
  Hashtbl.replace employees record.empno record;
 
  (* lookup by id *)
  begin
    try
      let rp = Hashtbl.find employees 132 in
      Printf.printf "employee number 132 is %s\n" rp.name
    with Not_found ->
      ()
  end;
 
  let jason = Hashtbl.find byname "Jason" in
  jason.salary <- jason.salary *. 1.035
 
(* Return true if the string s contains the given substring. *)
let contains s substring =
  try ignore (Str.search_forward (Str.regexp_string substring) s 0); true
  with Not_found -> false
 
let () =
  (* A filter function for hash tables, written as a fold. *)
  let grep f hash =
    Hashtbl.fold
      (fun key value result ->
         if f value then value :: result else result)
      hash [] in
 
  (* Select records matching criteria. *)
  let peons =
    grep (fun employee -> contains employee.title "peon") employees in
  let tsevens =
    grep (fun employee -> employee.age = 27) employees in
 
  (* Go through all records. *)
  let records = Hashtbl.fold (fun _ v a -> v :: a) employees [] in
  List.iter
    (fun rp ->
       Printf.printf "%s is age %d.\n" rp.name rp.age)
    (List.sort (fun r1 r2 -> compare r1.age r2.age) records)
 
(* Create an array of lists of records by age. *)
let byage = Array.create 150 []
let () =
  Hashtbl.iter
    (fun _ employee ->
       byage.(employee.age) <- employee :: byage.(employee.age))
    employees
 
(* Print all employees by age. *)
let () =
  Array.iteri
    (fun age emps ->
       match emps with
         | [] -> ()
         | _ ->
             Printf.printf "Age %d: " age;
             List.iter (fun emp -> Printf.printf "%s " emp.name) emps;
             print_newline ())
    byage
 
(* Similar approach using List.map and String.concat. *)
let () =
  Array.iteri
    (fun age emps ->
       match emps with
         | [] -> ()
         | _ ->
             Printf.printf "Age %d: %s\n" age
               (String.concat ", " (List.map (fun r -> r.name) emps)))
    byage

[править] Reading and Writing Hash Records to Text Files

#load "str.cma";;
 
(* Define a list reference to contain our data. *)
let (list_of_records : (string, string) Hashtbl.t list ref) = ref []
 
(* Read records from standard input. *)
let () =
  let regexp = Str.regexp "\\([^:]+\\):[ \t]*\\(.*\\)" in
  let record = ref (Hashtbl.create 0) in
  begin
    try
      while true do
        let line = read_line () in
        if Str.string_match regexp line 0
        then
          let field = Str.matched_group 1 line in
          let value = Str.matched_group 2 line in
          Hashtbl.replace !record field value
        else
          (list_of_records := !record :: !list_of_records;
           record := Hashtbl.create 0)
      done
    with End_of_file ->
      if Hashtbl.length !record > 0
      then list_of_records := !record :: !list_of_records
  end
 
(* Write records to standard output. *)
let () =
  List.iter
    (fun record ->
       Hashtbl.iter
         (fun field value -> Printf.printf "%s: %s\n" field value)
         record;
       print_newline ())
    !list_of_records

[править] Printing Data Structures

(* If you are in the OCaml toplevel, simply enter an expression to
   view its type and value. *)
# let reference = ref ( [ "foo", "bar" ],
                        3,
                        fun () -> print_endline "hello, world" );;
val reference : ((string * string) list * int * (unit -> unit)) ref =
  {contents = ([("foo", "bar")], 3, <fun>)}
 
(* From within your own programs, use the Std.print and Std.dump
   functions from the Extlib library, available at
   http://ocaml-lib.sourceforge.net/ *)
# Std.print reference;;
(([("foo", "bar")], 3, <closure>))
span class="sy0"> - : unit = ()
# Std.dump reference;;
span class="sy0"> - : string = "(([(\"foo\", \"bar\")], 3, <closure>))"

[править] Copying Data Structures

(* Immutable data structures such as int, char, float, tuple, list, Set,
   and Map can be copied by assignment. *)
let v2 = v1
let r2 = ref !r1
 
(* Objects can be shallow-copied using Oo.copy. *)
let o2 = Oo.copy o1
 
(* Several built-in types include copy functions. *)
let a2 = Array.copy a1
let h2 = Hashtbl.copy h1
let s2 = String.copy s1
 
(* Any data structure can be deep-copied by running it through Marshal,
   though this is not very efficient. *)
let (copy : 'a -> 'a) =
  fun value ->
    Marshal.from_string
      (Marshal.to_string value [Marshal.Closures])
      0

[править] Storing Data Structures to Disk

let () =
  (* Store a data structure to disk. *)
  let out_channel = open_out_bin "filename" in
  Marshal.to_channel out_channel data [];
  close_out out_channel;
 
  (* Load a data structure from disk. *)
  let in_channel = open_in_bin "filename" in
  let data = Marshal.from_channel in_channel in
  (* ... *)
  ();;
 
#load "unix.cma";;
let () =
  (* Store a data structure to disk, with exclusive locking. *)
  let out_channel = open_out_bin "filename" in
  Unix.lockf (Unix.descr_of_out_channel out_channel) Unix.F_LOCK 0;
  Marshal.to_channel out_channel data [];
  close_out out_channel;
 
  (* Load a data structure from disk, with shared locking. *)
  let in_channel = open_in_bin "filename" in
  Unix.lockf (Unix.descr_of_in_channel in_channel) Unix.F_RLOCK 0;
  let data = Marshal.from_channel in_channel in
  (* ... *)
  ()

[править] Transparently Persistent Data Structures

(* See recipes 14.8 and 14.9 for examples of (mostly) transparent
   persistence using DBM and Marshal in a type-safe manner. *)

[править] Program: Binary Trees

(* bintree - binary tree demo program *)
 
type 'a tree = { value : 'a;
                 left  : 'a tree option;
                 right : 'a tree option }
 
let rec string_of_tree tree =
  Printf.sprintf "{ value = %d; left = %s; right = %s }"
    tree.value
    (match tree.left with
       | None -> "None"
       | Some tree -> Printf.sprintf "Some (%s)" (string_of_tree tree))
    (match tree.right with
       | None -> "None"
       | Some tree -> Printf.sprintf "Some (%s)" (string_of_tree tree))
 
(* insert given value into proper point of
   provided tree.  If no tree provided,
   fill one in for our caller. *)
let rec insert tree value =
  match tree with
    | None -> { value = value; left = None; right = None }
    | Some tree ->
        if tree.value > value
        then { value = tree.value;
               left  = Some (insert tree.left value);
               right = tree.right }
        else if tree.value < value
        then { value = tree.value;
               left  = tree.left;
               right = Some (insert tree.right value) }
        else tree
 
(* recurse on left child,
   then show current value,
   then recurse on right child. *)
let rec in_order tree =
  match tree with
    | None -> ()
    | Some tree ->
        in_order tree.left;
        print_int tree.value;
        print_string " ";
        in_order tree.right
 
(* show current value,
   then recurse on left child,
   then recurse on right child. *)
let rec pre_order tree =
  match tree with
    | None -> ()
    | Some tree ->
        print_int tree.value;
        print_string " ";
        pre_order tree.left;
        pre_order tree.right
 
(* recurse on left child,
   then recurse on right child,
   then show current value. *)
let rec post_order tree =
  match tree with
    | None -> ()
    | Some tree ->
        post_order tree.left;
        post_order tree.right;
        print_int tree.value;
        print_string " "
 
(* find out whether provided value is in the tree.
   if so, return the node at which the value was found.
   cut down search time by only looking in the correct
   branch, based on current value. *)
let rec search tree value =
  match tree with
    | Some tree ->
        if tree.value = value
        then Some tree
        else search (if value < tree.value then tree.left else tree.right) value
    | None -> None
 
(* reference to the root of the tree *)
let root = ref None
 
(* first generate 20 random inserts *)
let () =
  Random.self_init ();
  for n = 0 to 19 do
    root := Some (insert !root (Random.int 1000))
  done
 
(* now dump out the tree all three ways *)
let () =
  print_string "Pre order: "; pre_order !root; print_newline ();
  print_string "In order: "; in_order !root; print_newline ();
  print_string "Post order: "; post_order !root; print_newline ()
 
(* prompt until EOF *)
let () =
  try
    while true do
      let line = read_line () in
      let num = int_of_string line in
      let found = search !root num in
      match found with
        | Some tree ->
            Printf.printf "Found %d at %s, %d\n"
              num
              (string_of_tree tree)
              tree.value
        | None ->
            Printf.printf "No %d in the tree\n" num
    done
  with End_of_file ->
    ()