I’m trying out the visitors ppx. I would like to generate an iter visitor class within a functor. For being concrete let’s say I want a binary search tree parametrized by an ordered type:
module type Ordered = sig
type t
type order = Leq | Gtr
val compare : t -> t -> order
end
module BT (Elt:Ordered) = struct
type t = Leaf | Node of {left:t; value:Elt.t; right:t}
[@@deriving visitors {variety="iter"}]
end
When I try this, I can’t get it to compile, and I think it’s because there is no information about Elt.t available. I think I’m not really getting it; is there a way to make this work? In the manual, some tricks are discussed that involve making parametrized types, but I don’t see how to apply this here.
first. Making BT into a non-functor and replacing Elt.t with int compiles.
I tried also adding a deriving annotation to the abstract type t in the Ordered signature, but this is rejected since annotations in signatures are not supported.
module type Ordered = sig
type t
type order = Leq | Gtr
val compare : t -> t -> order
end
module BT (Elt:Ordered) = struct
type et = Elt.t
[@@deriving visitors {name="eliter"; variety="iter"}]
type t = Leaf | Node of {left:t; value:et; right:t}
[@@deriving visitors {variety="iter"}]
end;;
I’m not sure yet if this will work out but I guess it’s a start!
Hmmhmm, I don’t use visitors but one solution is to enforce Ordered to have an iter function (and explicit this constraint by your hands because visitors does not support annotation in signature). Then, BT.t will use this iter function.
I’m not sure if visitors will pick up a function defined in a module by name; I think it looks only at ancestor classes?! Anyway, for now I think the approach above works ok. I get some warnings about undeclared virtual methods but in principle the visitor class for t works. I was able to define a print function using it. It ended up basically as long as something written directly from scratch, though.
The problem is that the visitor deriver picks a name visit_* for each type. For Elt.t, it unfortunately picks visit_t … which is also the name to visit t, hence clash.
The solution is simple, just instruct visitor to pick another name:
module BT (Elt:Ordered) = struct
type t = Leaf | Node of {left:t; value:(Elt.t [@name "elt"]); right:t}
[@@deriving visitors {variety="iter"}]
end ;;
Hi, thanks for the fix. I tried it but I still get a warning. Here is what I have now:
module type Ordered = sig
type t
type order = Leq | Gtr
val compare : t -> t -> order
val print : t -> unit
end
module BT (Elt:Ordered) = struct
type t = Leaf | Node of {left:t; value:(Elt.t [@name "elt"]); right:t}
[@@deriving visitors {variety="iter"}]
end
The compilation warning is:
Warning 17: the virtual method visit_elt is not declared.
I should add: the code does compile, unlike my initial attempts! But it does not completely eliminate the warnings that my previous solution generated.
Ok, so one thing I can do is declare the values to be opaque by putting value:(Elt.t [@opaque]). Then no virtual method is generated and thus no warning appears, but I can still define a method visit_elt by hand whenever I need to do something with the elements in application code. In my present use case this seems the right thing to do.
For reference, the completed code for a print function:
module BT (Elt:Ordered) = struct
type t = Leaf | Node of {left:t; value:(Elt.t [@opaque]); right:t}
[@@deriving visitors {variety="iter"}]
let rec add el = function
| Leaf -> Node {left=Leaf; value=el; right=Leaf}
| Node {left; value; right} ->
match Elt.compare value el with
| Elt.Leq -> Node {left; value; right=add el right}
| Elt.Gtr -> Node {left=add el left; value; right}
let print t =
let v = object (self:'self)
inherit [_] iter as super
method visit_elt env el =
for _ = 1 to env do print_string " " done;
Elt.print el;
print_newline ()
method visit_t env = function
| Leaf -> ()
| Node {left; value; right} ->
self#visit_t (env+1) left;
self#visit_elt env value;
self#visit_t (env+1) right;
end in
v#visit_t 0 t
end
The warning is expected (maybe it should be disabled by default in those cases). Have you actually tried using it ? Your previous version was just a more complicated workaround for doing the same thing (generating another name for visit_elt.
[@opaque] does something different: it removes all the calls to visit_elt and ensure that it will be never visited unless you redefine the methods yourself … which is exactly what you do here, and why it works. If you add more constructors, it will not, which sort of defeats the point of using visitors.
Yes, I did try the version with [@name "elt"] and it works. But to make use of the virtual method visit_elt later I also have to redefine it. (E.g. in the print function). So it’s not less work here.
I don’t quite get why it’s not a good idea to forego generating the virtual method. Is it because I might want other uses of the iterator class in which I want to use the inherited do-nothing version of the visit_elt method by default? I played around and the last sentence does not seem to make sense: there is no method visit_elt auto-generated, I always have to write it by hand after inheriting, even if it does nothing. What is the non-declared virtual method then good for?
Let’s say we want to collect all the elements in an Hashtable.
My version:
let collect =
let v = object
inherit [_] iter
method visit_elt env el = Hashtbl.add env el ()
end in
fun x -> let h = Hashtbl.create 17 in v#visit_t h x; h
Your version with opaque will also have to redefine visit_t because the default version will not call visit_elt at all. If you use opaque, you might as well not use a visitor at all for such a simple example, because you will have to write the recursive definition each time!
You don’t have to “redefine” elt, you have to define it, since there is no recursive call to make, there is no base definition.
Ok, I think I’m starting to get it. In your version, visit_elt is called somewhere in visit_t but since it’s missing from the class, the compiler emits a warning. I did not get where the warning exactly came from before – I guess that’s what you get for using code generation…
Anyway, making the type opaque will then not generate the calls at all, and I have to write all of them myself, which does not really save work. Ok.
It seems that the warning 17 is then often emitted during normal use of visitors whenever a type is not locally available. Maybe it should indeed be suppressed then in the generated code. To me the warning suggested that something is not going as intended / I am using the ppx wrong.
Also, RTFM applies. The point here is stated in the middle of page 35 of the manual linked above.