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: Jacques Garrigue <garrigue@m...>
Subject: Re: [Caml-list] environment idiom

> 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.


> 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");;

This seems a bit of an overkill: I would rather write directly
  let env = [`Banana "b"; `Apple "a"; `Pear "p"]
Which would give you more or less the same typing.

> 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.

A common way to do it would be to have a class defining defaults,
and inherit and override it in your environment object.
I'm not sure of how you intend to use your environment.
But you're right that it is difficult to make changes incremental,
since ocaml have no incrementally extendable records.

Note also that while you cannot extend an object, you can extend a
class, and you can use local modules to define classes locally.
Yet, you cannot pass classes (or modules) to functions, so this does not
solve your problem.

> 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"?

No: any variant type is a subtype of a variant type containing more
keys, so you would be able to cheat anyway.

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

I think you've described them correctly: objects offer exact typing,
but cannot be extended incrementally, and lists of porlymorphic
variants enforce typing but do not guarantee what keys are defined.

In terms of efficiency, objects generate more code, but performance should
be comparable.

Note that ocaml contains a third way to do that, which in some cases is
more natural. You can use labelled arguments. This means that you must
pass all the arguments explicitly, but you are no longer restricted by
their order. Some arguments may be optional.
Depending on your goal, this may be the safest way to pass parameters.

let bar ~pear s = pear^s
let foo ~apple ~banana ~pear s = bar ~pear (apple^banana^s)

You may look at code in lablgtk2 for clever ways to handle long lists
of parameters this way. (But this doesn't fit your web server

A last way, which could work very well with your web server example,
is to use records in place of objects, and update them using
with. Then you can use clever typing.

module Option : sig
  type (+'a,+'b) t
  type abs = [`abs|`pre]
  type pre = [`pre]
  val none : ('a, abs) t
  val some : 'a -> ('a, 'b) t
  val get : ('a, pre) t -> 'a
end = struct
  type ('a,'b) t = 'a option
  type abs = [`abs|`pre]
  type pre = [`pre]
  let none = None
  let some x = Some x
  let get = function Some x -> x | None -> assert false

open Option

type ('a,'b,'c) env =
  {apple: (string, 'b) t; banana: (string, 'a) t; pear: (string, 'c) t}

# let empty = {apple=none; banana=none; pear=none}
val empty : (abs, abs, abs) env

# let e1 = {empty with pear=some "Williams"}
val e1 : (abs, abs, 'a) env
# let e2 = {e1 with apple=some "Golden"; banana=some "Plantin"}
val e2 : ('a, 'b, 'c) env

# let bar env s =
    get env.pear ^ s
val bar : ('a, 'b, pre) env -> string -> string
# let foo env s =
   bar env (get ^ get env.banana ^ s)
val foo : (pre, pre, pre) env -> string -> string

# foo e2 "!";;
- : string = "WilliamsGoldenPlantin!"

# foo e1;;
This expression has type (Option.abs, Option.abs, 'a) env
but is here used with type (Option.pre, Option.pre, Option.pre) env
Type Option.abs = [ `abs | `pre ] is not compatible with type
  Option.pre = [ `pre ] 

Polymorphic variants are not essential here.
They just allow one to forget about some fields of the environment
without physically modifying it. For instance:

# let forget_pear env = (env :> (_,_,abs) env);;
val forget_pear : ('a, 'b, [< abs ]) env -> ('a, 'b, abs) env

If you don't need this kind of operation, you could choose a simpler
interface, which would be enough in most cases.
module Option : sig
  type (+'a,+'b) t
  type abs
  type pre
  val none : ('a, abs) t
  val some : 'a -> ('a, 'b) t
  val get : ('a, pre) t -> 'a

By the way, I had thought of a clever way to obtain nicer types:
type 'u env =
  {banana: (string, 'a) t; apple: (string, 'b) t; pear: (string, 'c) t}
  constraint 'u = <banana:'a; apple:'b; pear:'c>
But after fiddling with it I discovered a serious bug in the type
checker. Please do not use constraints on type variables that do not
appear in the type itself until this is solved. Sorry for the

Jacques Garrigue