Version française
Home     About     Download     Resources     Contact us    
Browse thread
environment idiom
[ Home ] [ Index: by date | by threads ]
[ Search: ]

[ Message by date: previous | next ] [ Message in thread: previous | next ] [ Thread: previous | next ]
Date: -- (:)
From: HENRIKSON, JEFFREY <JEFHEN@S...>
Subject: environment idiom
caml-list,

I am interested in the idiom of passing a number of parameters by some
kind of "environment" variable.  Think of a web server with hundredes of
functions for processing markup and other things, only 3 of which need
to detect the browser.  It's bad maintainability to explicitly pass
browserid through hundreds of functions which don't use it.  And of
course, we must separate the state of the calling threads so as to not
cheat with global variables or some such thing.

There seem to be two main candidates for such an idiom in Ocaml, objects
and polymorphic variants.  The object way, roughly:

let bar env s =
  env#pear ^ s;;

let foo env s =
  let x = env#apple in
  let y = env#banana in
  bar env (x^y^s);;

As long as we never name the classes the compiler keeps track of which
environment methods the foo call chain has by creating ad hoc classes:

#  val bar : < pear : string; .. > -> string -> string = <fun>
#  val foo :
  < apple : string; banana : string; pear : string; .. > -> string ->
string =
  <fun>

And the polymorphic variant way, roughly:


let h = Hashtbl.create 10;;

Hashtbl.add h `Banana (`Banana "b");;
Hashtbl.add h `Apple (`Apple "a");;
Hashtbl.add h `Pear (`Pear "p");;


let get_apple h =
  try match Hashtbl.find h `Apple with
      `Apple n -> n
    | _ -> failwith "no apple config key"
  with Not_found -> failwith "no apple config key";;

let get_banana h =
  try match Hashtbl.find h `Banana with
      `Banana n -> n
    | _ -> failwith "no banana config key"
  with Not_found -> failwith "no banana config key";;

let get_pear h =
  try match Hashtbl.find h `Pear with
      `Pear n -> n
    | _ -> failwith "no pear config key"
  with Not_found -> failwith "no pear config key";;


let bar env s =
    (get_pear env) ^ s;;
#   val bar : ([> `Pear ], [> `Pear of string ]) Hashtbl.t -> string ->
string =  <fun>

let foo env s =
  let x = get_apple env in
  let y = get_banana env in
  bar env (x^y^s);;
#       val foo :
  ([> `Apple | `Banana | `Pear ],
   [> `Apple of string | `Banana of string | `Pear of string ])
  Hashtbl.t -> string -> string = <fun>

foo h "5";;
# - : string = "pab5"



Each of these idioms has its own advantage:

In the object way the compiler verifies that the functions are passed
objects which contain all their needed configuration keys.  But if I
understand correctly, we must at some point construct an environment
object which has _all_ the keys, even if we don't know them yet.  We can
add by mutation, but we cannot simply leave them out and add them as we
get to functions which need them.  This is because while we can write


object
  method apple = "a"
  method banana = "b"
end;;
- : < apple : string; banana : string > = <obj>


we cannot inherit anonymously:


let add_pear env =
  object
    inherit env
    method pear = "p"
  end;;

#         Characters 40-43:
      inherit env
              ^^^
Unbound class
env


In the pv way the construction can be made incremental.  Ie, if we
changed the hashtable to a list or immutable queue, we could add keys as
we go.  But at least as I have it set up, the variants are not placing
restrictions on the existence of keys in the environment, other than
saying "we can understand at least this many keys," which is of course
meaningless.  Is there a way to turn the typing around to say "we
require at least these keys"?

In general, what are the typing and run-time limitations around each
way?

Regards,


Jeff Henrikson