Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Bindlib and Conchon & Filliâtre's Hashcons lib #37

Open
grayswandyr opened this issue Jul 5, 2024 · 10 comments
Open

Using Bindlib and Conchon & Filliâtre's Hashcons lib #37

grayswandyr opened this issue Jul 5, 2024 · 10 comments

Comments

@grayswandyr
Copy link

Hi,
thanks for this nice library.
I am currently doing some testing, using a simple representation of first-order logic. My aim would be to combine the use of Bindlib with Conchon & Filliâtre's Hashcons library (article PDF), in particular using the Make functor. Alas I'm a but stuck, even after trying to get inspiration from your comments in #33 as well as your example. Would you have any hint to share? Thanks.

type term_node = Var of term Bindlib.var | App of Symbol.t * term list
and term = term_node H.hash_consed
and op = And
and t_node = Atom of Symbol.t * term list | Not of t | Bin of t * op * t
and t = t_node H.hash_consed
@craff
Copy link
Collaborator

craff commented Jul 5, 2024 via email

@craff
Copy link
Collaborator

craff commented Jul 5, 2024 via email

@grayswandyr
Copy link
Author

Thank you so much, Christophe, for your prompt answer. Sorry for the missing binder, I had edited the code to make it simpler and removed the "for all" quantifier, here it is for the sake of completeness:

type term_node = Var of term Bindlib.var | App of Symbol.t * term list
and term = term_node H.hash_consed
and op = And
and t_node = Atom of Symbol.t * term list | Not of t | Bin of t * op * t | Forall of (term, t) binder
and t = t_node H.hash_consed

The code you submitted is indeed a bit tricky, I'll have to study it a little bit.

In the example, you're also using a constructor for hashed variables. Do you think it would be feasible to keep the original type as is, as Rodolphe had sketched in his answer to #33 ?

Do you see any advantage or drawback to one or the other approaches?

@rlepigre
Copy link
Owner

rlepigre commented Jul 5, 2024

Rodolphe: I guess you will read this, shall I push it ? It introduces a dependency on hashcons for one test and I don't know how modern opam handle this, but in the past it was an extra dependency for the library.

Sure, I approved your PR. It should be fine for the dependency: it should only be installed if tests are requested.

@rlepigre
Copy link
Owner

rlepigre commented Jul 5, 2024

In the example, you're also using a constructor for hashed variables. Do you think it would be feasible to keep the original type as is, as Rodolphe had sketched in his answer to #33 ?

I think it should be possible to use that approach indeed.

Do you see any advantage or drawback to one or the other approaches?

To me, the sole advantage of the approach I suggested (use a hash-table to store the hashes of bound variables) is that it avoids the need to add an extra and rather ad hoc constructor. A potential drawback is that additional insertions and lookups in a hash-table are required, so this might be less efficient (we should probably try both and benchmark).

@craff
Copy link
Collaborator

craff commented Jul 6, 2024 via email

@grayswandyr
Copy link
Author

grayswandyr commented Jul 8, 2024

Hi,
adding a special constructor is perhaps a bit harder to explain to other developers on the same project who don't deal with this part... so I'm trying to implement the "second" approach (i.e. without a supplemental constructor for hashed variables) from #33 to FOL, I hit a few issues, not completely solved. I'd be glad to get your feedback (and possibly error spotting), it might be useful to others too.

  • as far as I can see, 2 recursive modules must be used per "inner" type (i.e. t_node and term_node). An added difficulty is that bound variables (yielded by unbind) are added to the bound-var hashtable in the module for formulas (in the t_node hash function on case Forall), but the said table is accessed in the hash function on terms (that is, in another module). My solution was to make the hash_var function (which holds the said hashtable in its environment) accessible from outside.
  • For most equal cases, we want to rely on physical equality, as in C&F's article, but I suppose we can't use this for the Var nor for equal_binders (that I don't see how to write, actually).
open Containers (* Simon Cruanes' extension to the Stdlib *)

module Fol = struct
  module B = Bindlib
  module HC = Hashcons

  (* a [Symbol.t] is a string with an efficient [equal] operation (implemented as a hash-consed string under the hood) *)

  (* terms *)
  type term_node = Var of term Bindlib.var | App of Symbol.t * term list
  and term = term_node HC.hash_consed
  and op = And | Or

  (* formulas *)
  and t_node =
    | Atom of Symbol.t * term list
    | Not of t
    | Bin of t * op * t
    | Forall of (term, t) B.binder

  and t = t_node HC.hash_consed

  module rec Hashed_t_node : (sig
    include Hashtbl.HashedType

    val hash_var : term B.var -> int
  end
  with type t = t_node) = struct
    type t = t_node

    module BoundVars = Hashtbl.Make (struct
      type t = term B.var

      let hash = B.hash_var
      let equal = B.eq_vars
    end)

    let bound_vars = BoundVars.create 251

    let next_var : unit -> int =
      let counter = ref (-1) in
      fun _ ->
        incr counter;
        !counter

    let hash_var x =
      (* Only bound variables have a stored hash. *)
      try BoundVars.find bound_vars x with Not_found -> Bindlib.hash_var x

    let hash = function
      | Atom (s, ts) ->
          (* the Container.Hash module implements combinators for hash computation; [poly] is [Hashtbl.hash] *)
          Hash.(triple poly Symbol.hash (list int))
            (`Atom, s, List.map (fun t -> t.HC.hkey) ts)
      | Not f -> Hash.(pair poly int) (`Not, f.HC.hkey)
      | Bin (f1, op, f2) ->
          Hash.(quad poly int poly int) (`Bin, f1.HC.hkey, op, f2.HC.hkey)
      | Forall f ->
          let x, subf = B.unbind f in
          BoundVars.add bound_vars x (next_var ());
          Hash.(pair poly int) (`Forall, subf.HC.hkey)

    (* ??? *)
    let equal_binders : (term, _) B.binder -> (term, _) B.binder -> bool =
      assert false

    let equal f1 f2 =
      match (f1, f2) with
      | Atom (s1, ts1), Atom (s2, ts2) ->
          (* Equal.physical is ( == ) *)
          Symbol.equal s1 s2 && Equal.(list physical) ts1 ts2
      | Not f1, Not f2 -> Equal.physical f1 f2
      | Bin (f11, op1, f12), Bin (f21, op2, f22) ->
          Equal.physical op1 op2 && Equal.physical f11 f21
          && Equal.physical f12 f22
      | Forall f1, Forall f2 -> equal_binders f1 f2
      | _ -> false
  end

  and Hashed_t_node_table : (HC.S with type key = t_node) =
    HC.Make (Hashed_t_node)

  and Hashed_term_node : (Hashtbl.HashedType with type t = term_node) = struct
    type t = term_node

    let hash = function
      | Var x -> Hash.(pair poly Hashed_t_node.hash_var) (`Var, x)
      | App (f, ts) ->
          Hash.(triple poly Symbol.hash (list int))
            (`App, f, List.map (fun t -> t.HC.hkey) ts)

    let equal t1 t2 =
      match (t1, t2) with
      | Var x1, Var x2 -> B.eq_vars x1 x2
      | App (f1, ts1), App (f2, ts2) ->
          Symbol.equal f1 f2 && Equal.(list physical) ts1 ts2
      | _ -> false
  end

  and Hashed_term_node_table : (HC.S with type key = term_node) =
    HC.Make (Hashed_term_node)
end

@craff
Copy link
Collaborator

craff commented Jul 8, 2024 via email

@grayswandyr
Copy link
Author

I think I get your point about additional constructors and environments, now, thanks.

I attached the adapted example with extensible variants.

I wasn't able to find it...?

@craff
Copy link
Collaborator

craff commented Jul 9, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants