module Bindlib:sig..end
The Bindlib library provides support for free and bound variables in the
OCaml language. The main application is the construction of abstract types
containing a binding structure (e.g., abstract syntax trees).
The Bindlib library provides two type constructors for building abstract
syntax trees: 'a var and ('a,'b) binder. Intuitively, 'a var will be
a representation for a free variable of type 'a, and ('a,'b) binder a
represention for a term of type 'b depdening on a variable (or value) of
type 'a (the type ('a,'b) binder can be seen as 'a -> 'b). Note that
types 'a mvar and ('a,'b) mbinder are provided for handling arrays of
variables.
type 'a var
Type of a free variable of type 'a.
type'amvar ='a var array
Type of an array of variables of type 'a.
type (-'a, +'b) binder
Type of a binder for an element of type 'a into an element of type 'b.
In terms of higher-order abstract syntax, it can be seen as 'a -> 'b.
type ('a, 'b) mbinder
Type of a binder for an array of elements of type 'a into an element of
type 'b.
As an example, we give bellow the definition of a simple representation of the terms of the lambda-calculus.
type term =
| Var of term var
| Abs of (term, term) binder
| App of term * term
val subst : ('a, 'b) binder -> 'a -> 'bsubst b v substitutes (using application) the variable bound by b with
the value b. This is a very efficient operations.
val msubst : ('a, 'b) mbinder -> 'a array -> 'bmsubst b vs substitutes (using application) the array of variables bound
by b with the values vs. This is a very efficient operations. However,
the length of the vs array should match the arity of the binder (see the
function mbinder_arity).
Comming back to our lambda-calculus example, we can define the evaluation
of a lambda-term as a simple recursive function using subst.
let rec eval : term -> term = fun t ->
match t with
| App(f,a) ->
begin
match eval f with
| Abs(b) -> eval (subst b a)
| _ -> t
end
| _ -> t
val new_var : ('a var -> 'a) -> string -> 'a varnew_var mkfree name creates a new variable using a function mkfree and
a name. The mkfree function is used to inject variables in the type of
the corresponding elements. It is a form of syntactic wrapper.
val new_mvar : ('a var -> 'a) -> string array -> 'a mvarnew_mvar mkfree names creates a new array of variables using a function
mkfree (see new_var) and a name.
Following on our example of the lambda-calculus, the mkfree function for
variables of type term var could be defined as follows.
let mkfree : term var -> term = fun x -> Var(x)
val name_of : 'a var -> stringname_of x returns a printable name for variable x.
val unbind : ('a var -> 'a) -> ('a, 'b) binder -> 'a var * 'bunbind mkfree b breaks down the binder b into a variable, and the term
in which this variable is now free. Note that the usual mkfree function
is required, since unbind needs to create a new variable (its name will
be that of the previously bound variable).
val unmbind : ('a var -> 'a) -> ('a, 'b) mbinder -> 'a mvar * 'bunmbind mkfree b breaks down the binder b into variables, and the term
in which these variables are now free. Again, the usual mkfree function
is required, and the name of the new variables is based on that of all the
variables that were previously bound.
An usual use of unbind is the wrinting of pretty-printing functions. The
function given bellow transforms a lambda-term into a string. Note that
the name_of function is used for variables.
let rec to_string : term -> string = fun t ->
match t with
| Var(x) -> name_of x
| Abs(b) -> let (x,t) = unbind mkfree b in
"\\" ^ name_of x ^ "." ^ to_string t
| App(t,u) -> "(" ^ to_string t ^ ") " ^ to_string u One of the main design priciple of the Bindlib library is efficiency. To
obtain very fast substitutions, a price is paid at the construction of the
terms. Indeed, binders (i.e., element of type ('a,'b) binder) cannot be
defined directly. Instead, they are put together in the type 'a bindbox.
It correspond to a term of type 'a with free variables that may still be
bound in the future.
type +'a bindbox
Type of a term of type 'a under construction. Using this representation,
the free variable of the term can be bound easily.
val box_of_var : 'a var -> 'a bindboxbox_of_var x builds a 'a bindbox from the 'a var x.
val box : 'a -> 'a bindboxbox e puts the value e into the 'a bindbox type, assuming that it is
closed. Thus, if e contains variables, then they will not be considered
free. This means that no variable of e will be available for binding.
val apply_box : ('a -> 'b) bindbox -> 'a bindbox -> 'b bindboxapply_box bf ba applies the boxed function bf to a boxed argument ba
inside the 'a bindbox type. This function is useful for constructing new
expressions by applying a function with free variables to an argument with
free variables. Note that the 'a bindbox type is an applicative functor.
Its application operator (sometimes written "<*>") is apply_box, and its
unit (sometimes called "pure") is box.
val box_apply : ('a -> 'b) -> 'a bindbox -> 'b bindboxbox_apply f ba applies the function f to a boxed argument ba. It is
equivalent to apply_box (box f) ba, but is more efficient.
val box_apply2 : ('a -> 'b -> 'c) ->
'a bindbox -> 'b bindbox -> 'c bindboxbox_apply2 f ba bb applies the function f to two boxed arguments ba
and bb. It is equivalent to apply_box (apply_box (box f) ba) bb but it
is more efficient.
val bind : ('a var -> 'a) ->
string ->
('a bindbox -> 'b bindbox) ->
('a, 'b) binder bindboxbind mkfree name f constructs a boxed binder from the function f. Note
that name and mkfree are required to build the bound variable.
val mbind : ('a var -> 'a) ->
string array ->
('a bindbox array -> 'b bindbox) ->
('a, 'b) mbinder bindboxmbind mkfree names f constructs a boxed binder from function f, using
mkfree and names to build the bound variables.
As mentioned earlier, terms with bound variables can only be build in the
'a bindbox type. To ease the writing of terms, it is a good practice to
define "smart constructors" at the 'a bindbox level. Coming back to our
lambda-calculus example, we can give the following smart constructors.
let var : term var -> term bindbox =
fun x -> box_of_var x
let abs : string -> (term bindbox -> term bindbox) -> term bindbox =
fun x f -> box_apply (fun b -> Abs(b)) (bind mkfree x f)
let app : term bindbox -> term bindbox -> term bindbox =
fun t u -> box_apply2 (fun t u -> App(t,u)) t u
val unbox : 'a bindbox -> 'aunbox e can be called when the construction of a term is finished (e.g.,
when the desired variables have all been bound).
We can then easily define terms of the lambda-calculus as follows.
let id : term = (* \x.x *)
unbox (abs "x" (fun x -> x))
let fst : term = (* \x.\y.x *)
unbox (abs "x" (fun x -> abs "y" (fun _ -> x)))
let omega : term = (* (\x.(x) x) \x.(x) x *)
let delta = abs "x" (fun x -> app x x) in
unbox (app delta delta)
val vbind : ('a var -> 'a) ->
string ->
('a var -> 'b bindbox) ->
('a, 'b) binder bindboxvbind mkfree name f constructs a boxed binder from the function f, as
the bind function does. The difference here is that the domain of f is
'a var, and not 'a bindbox.
val mvbind : ('a var -> 'a) ->
string array ->
('a var array -> 'b bindbox) ->
('a, 'b) mbinder bindboxmvbind mkfree names f constructs a boxed binder from the function f as
the mbind function does. However, the domain of f is 'a var, and not
'a bindbox.
Using the vbind function instead of the bind function, we can give an
alternative smart constructor for lambda-abstraction. Note that we need to
use the box_of_var to use a variable when defining a term.
let abs_var : string -> (term var -> term bindbox) -> term bindbox =
fun x f -> box_apply (fun b -> Abs(b)) (vbind mkfree x f)
let delta : term = (* \x.(x) x *)
unbox (abs_var "x" (fun x -> app (var x) (var x)))
val bind_var : 'a var ->
'b bindbox -> ('a, 'b) binder bindboxbind_var x b binds the variable x in b to produce a boxed binder. In
fact, is used to implement bind and vbind.
val bind_mvar : 'a mvar ->
'b bindbox -> ('a, 'b) mbinder bindboxbind_mvar xs b binds the variables of xs in b to get a boxed binder.
In fact, bind_mvar is used to implement mbind and mvbind.
In general, it is not difficult to use the box and apply_box functions
to manipulate any kind of data in the 'a bindbox type. However, working
with these functions alone can be tedious. The functions provided here can
be used to manipulate standard data types in an optimised way.
val box_opt : 'a bindbox option -> 'a option bindboxbox_opt bo shifts the option type of bo into the bindbox.
val box_list : 'a bindbox list -> 'a list bindboxbox_list bs shifts the list type of bs into the bindbox.
val box_rev_list : 'a bindbox list -> 'a list bindboxbox_rev_list bs shifts the list type of bs into the bindbox, while
reversing the list (for efficiency).
val box_array : 'a bindbox array -> 'a array bindboxbox_array bs shifts the array type of bs into the bindbox.
val box_apply3 : ('a -> 'b -> 'c -> 'd) ->
'a bindbox ->
'b bindbox -> 'c bindbox -> 'd bindboxbox_apply3 is similar to box_apply2.
val box_apply4 : ('a -> 'b -> 'c -> 'd -> 'e) ->
'a bindbox ->
'b bindbox ->
'c bindbox -> 'd bindbox -> 'e bindboxbox_apply4 is similar to box_apply2 and box_apply3.
val box_pair : 'a bindbox -> 'b bindbox -> ('a * 'b) bindboxbox_pair ba bb is the same as box_apply2 (fun a b -> (a,b)) ba bb.
val box_triple : 'a bindbox ->
'b bindbox -> 'c bindbox -> ('a * 'b * 'c) bindboxbox_trible is similar to box_pair, but for triples.
module type Map =sig..end
Type of a module equipped with a map function.
module Lift(M:Map):sig..end
Functorial interface used to build lifting functions (i.e., functions that
permute the 'a bindbox type with another type constructor) for any type
equipped with a map function.
module type Map2 =sig..end
Type of a module equipped with a "binary" map function.
module Lift2(M:Map2):sig..end
Similar to the Lift functor, but handles "binary" map functions.
val prefix_of : 'a var -> stringprefix_of x returns the string prefix of variable x's name, which is
only the first part of its full name (obtained with name_of x). The full
name also contain an int suffix, which is defined as the longest suffix
of digits in the full name.
val suffix_of : 'a var -> intsuffix_of x returns the int suffix of variable x's name. It consists
in the longest digit suffix in the full name of x (see prefix_of).
val free_of : 'a var -> 'afree_of x injects variable x into the corresponding type, relying on
the mkfree function provided at the creation of the variable.
val hash_var : 'a var -> inthash_var x computes a hash for variable x. Note that this function can
be used with the Hashtbl module.
val compare_vars : 'a var -> 'b var -> intcompare_vars x y safely compares x and y. Note that it is unsafe to
compare variables with Pervasive.compare.
val eq_vars : 'a var -> 'b var -> booleq_vars x y safely computes the equality of x and y. Note that it is
unsafe to compare variables with the polymorphic equality function.
val copy_var : 'b var -> string -> ('a var -> 'a) -> 'a varcopy_var x name mkfree makes a copy of variable x, with a potentially
different name and mkfree function. However, the copy is treated exactly
as the original in terms of binding and substitution.
val binder_name : ('a, 'b) binder -> stringbinder_name returns the name of the variable bound by the binder.
val binder_occur : ('a, 'b) binder -> boolbinder_occur b tests whether the bound variable occurs in b.
val binder_constant : ('a, 'b) binder -> boolbinder_constant b tests whether the binder b is constant (i.e., its
bound variable does not occur).
val binder_closed : ('a, 'b) binder -> boolbinder_closed b test whether the binder b is closed (i.e., does not
contain any free variable).
val binder_rank : ('a, 'b) binder -> intbinder_rank b gives the number of free variables contained in b.
val binder_compose_left : ('a -> 'b) -> ('b, 'c) binder -> ('a, 'c) binderbinder_compose_left f b precomposes the binder b with the function f
without changing anything at the binding structure.
val binder_compose_right : ('a, 'b) binder -> ('b -> 'c) -> ('a, 'c) binderbinder_compose_rigth b f postcomposes the binder b with the function
f without changing anything at the binding structure.
val mbinder_arity : ('a, 'b) mbinder -> intmbinder_arity b gives the arity of the mbinder.
val mbinder_names : ('a, 'b) mbinder -> string arraymbinder_names b return the array of the names of the variables bound by
the mbinder b.
val mbinder_occurs : ('a, 'b) mbinder -> bool arraymbinder_occurs b returns an array of bool indicating if the variables
that are bound occur (i.e., are used).
val mbinder_constant : ('a, 'b) mbinder -> boolmbinder_constant b indicates whether the mbinder b is constant. This
means that none of its variables are used.
val mbinder_closed : ('a, 'b) mbinder -> boolmbinder_closed b indicates whether b is closed.
val mbinder_rank : ('a, 'b) mbinder -> intval is_closed : 'a bindbox -> boolis_closed b checks whether the bindbox b is closed.
val occur : 'a var -> 'b bindbox -> booloccur x b tells whether variable x occurs in the bindbox b.
val bind_apply : ('a, 'b) binder bindbox ->
'a bindbox -> 'b bindboxbind_apply bb barg substitute the boxed binder bb with the boxed value
barb in the bindbox type. This is useful for working with higher-order
variables, which may be represented as binders themselves.
val mbind_apply : ('a, 'b) mbinder bindbox ->
'a array bindbox -> 'b bindboxmbind_apply bb bargs substitute the boxed binder bb with the boxed
array of values barbs in the bindbox type. This is useful for working
with higher-order variables.
val fixpoint : (('a, 'b) binder, ('a, 'b) binder) binder
bindbox -> ('a, 'b) binder bindboxfixpoint bb constructs a binder fixpoint. This very advanced feature can
be used to build recursive definitions (like with OCaml's "let rec").
It is sometimes convenient to work in a context for variables, for example
when one wishes to reserve variable names. The Bindlib library provides
a type of contexts, together with functions for creating variables and for
binding variables in a context.
type ctxt
Type of a context.
val empty_ctxt : ctxtempty_ctxt denotes the empty context.
val new_var_in : ctxt ->
('a var -> 'a) -> string -> 'a var * ctxtnew_var_in ctxt mkfree name is similar to new_var mkfree name, but the
variable names is chosen not to collide with the context ctxt. Note that
the context that is returned contains the new variable name.
val new_mvar_in : ctxt ->
('a var -> 'a) -> string array -> 'a mvar * ctxtnew_mvar_in ctxt mkfree names is similar to new_mvar mkfree names, but
it handles the context (see new_var_in).
val unbind_in : ctxt ->
('a var -> 'a) ->
('a, 'b) binder -> 'a var * 'b * ctxtunbind_in ctxt mkfree b is similar to unbind mkfree b, but it handles
the context (see new_mvar_in).
val unmbind_in : ctxt ->
('a var -> 'a) ->
('a, 'b) mbinder -> 'a mvar * 'b * ctxtmunbind_in ctxt mkfree b is like munbind mkfree b, but it handles the
context (see new_mvar_in).
val bind_in : ctxt ->
('a var -> 'a) ->
string ->
('a bindbox -> ctxt -> 'b bindbox) ->
('a, 'b) binder bindboxbind_in ctxt mkfree name f is like bind mkfree name f, but it handles
the context.
val mbind_in : ctxt ->
('a var -> 'a) ->
string array ->
('a bindbox array -> ctxt -> 'b bindbox) ->
('a, 'b) mbinder bindboxmbind_in ctxt mkfree names f is similar to mbind mkfree names f, but
it handles the context.
val reset_counter : unit -> unitreset_counter () resets the unique identifier counter on which Bindlib
relies. This function should only be called when previously generated data
(e.g., variables) cannot be accessed anymore.
val dummy_bindbox : 'a bindboxdummy_bindbox can be used for initialising structires (e.g., arrays). If
unbox is called on a data structure containing a dummy_bindbox, the
exception Failure "Invalid use of dummy_bindbox" is raised.
val binder_from_fun : string -> ('a -> 'b) -> ('a, 'b) binderbinder_from_fun name f transform a function into a binder. Note that the
function is only called when the binder is substituted (see subst). This
is not the recommended way of building binders. Nonetheless, it is useful
for simple tasks such as contracting two binders into one, without copying
the whole structure (e.g., transform \x.\y.t(x,y) into \x.t(x,x)).
val mbinder_from_fun : string array -> ('a array -> 'b) -> ('a, 'b) mbindermbinder_from_fun is similar to binder_from_fun.