Search code examples
functional-programmingocaml

Creating an empty tree zipper, but the tree is not defined over being empty


I am given this module type, and I am asked to create an empty tree_zipper, but I think it's not possible with the following implementation of tree zipper:

module type TREE_ZIPPER = 
  sig
  type 'a ntree
  type 'a context
  type 'a tree_zipper
  val previous_exn : 'a tree_zipper -> 'a tree_zipper
  val next_exn : 'a tree_zipper -> 'a tree_zipper
  val up_exn : 'a tree_zipper -> 'a tree_zipper
  val down_exn : 'a tree_zipper -> 'a tree_zipper
  val update :'a tree_zipper -> 'a ntree -> 'a tree_zipper
  val insert_exn : 'a tree_zipper -> 'a ntree -> 'a tree_zipper
  val insert_down_exn : 'a tree_zipper -> 'a ntree -> 'a tree_zipper
  val insert_prev_exn : 'a tree_zipper -> 'a ntree -> 'a tree_zipper
  val insert_next_exn : 'a tree_zipper -> 'a ntree -> 'a tree_zipper
  val remove_exn : 'a tree_zipper -> 'a tree_zipper
  val from_ntree : 'a ntree -> 'a tree_zipper
end

and this module struct:

module NTreeZipper : TREE_ZIPPER = 
  struct
    type 'a ntree = Node of 'a * 'a ntree list
    type 'a context = | Top | Context of 'a ntree list * 'a * 'a context * 'a ntree list
    type 'a tree_zipper = TZ of 'a context * 'a ntree
    let previous_exn (TZ(c,t)) = (* on the left, not possible when context is top or left = []*)
      match c with
      | Top -> failwith "root: no left"
      | Context([], v, up, right) -> failwith "left of first child"
      | Context(l::left, v, up, right) -> TZ(Context (left, v, up, t::right), l)
      (* TZ (Context ([], "+", Top, [Node ("*", [Node ("c", []); Node ("d", [])])]),
      Node ("*", [Node ("a", []); Node ("b", [])])) *)
    let next_exn (TZ(c,t)) = (* on the right, not possible when context is top or right = []*)
      match c with
      | Top -> failwith "root: no right"
      | Context(left, v, up, []) -> failwith "right of last child" (*explain*)
      | Context(left, v, up, r::right) -> TZ (Context(t::left, v, up, right), r)
    let down_exn (TZ(c,t)) = 
      match t with (* go to first child *)
      | Node(_,[]) -> failwith "leaf: no children"
      | Node(v,d::ds) -> TZ (Context([],v, c,ds), d)
    let up_exn (TZ(c,t)) = 
      match c with
      | Top -> failwith "root: no parent"
      | Context(l, v, u,r) -> TZ (u, Node(v, (List.rev_append l (t::r))))
    let update (TZ(c,_)) t = TZ(c,t)
    let insert_exn (TZ(c,t)) t1 = (* in-place insert; t1 is a node *)
      match c with
      | Top -> failwith "insert on top"
      | Context (ls, v, u, rs) -> TZ (Context (ls, v, u, t::rs), t1)
    let insert_prev_exn (TZ(c,t)) t1 = 
      match c with
      | Top -> failwith "insert_prev on top"
      | Context(ls, v, u, rs) -> TZ (Context(t1::ls, v, u, rs), t);;
    let insert_next_exn (TZ(c,t)) t1 = 
      match c with
      | Top -> failwith "insert_next on top"
      | Context(ls, v, u, rs) -> TZ(Context(ls, v, u, t1::rs), t)
    (* move down and insert *)
    let insert_down_exn (TZ(c,t)) t1 = 
      match t with
      | Node (_,[]) -> failwith "insert_down on leaf"
      | Node(v,l) -> TZ(Context([],v, c,l), t1)
    (*Remove the focused subtree, and move to the right if possible, else to the left, else upward:*)
    let remove_exn (TZ(c,_)) = (*logic: if right put focus there, else if !right then focus in left, else up*) 
      match c with 
      | Top -> failwith "remove of top"
      | Context(ls,v,u,r::rs) -> TZ(Context(ls,v,u,rs), r) (*has right*)
      | Context(l::ls,v,u,[]) -> TZ(Context(ls,v,u,[]), l) (*has left*)
      | Context([],v,u,[]) -> TZ(u, Node(v,[])) (*has no right or left, [] is the t removed*)
    let from_ntree t = TZ (Top,t)
  end

With this implementation, is it possible to create an empty tree_zipper? Because ntree is not defined over being empty as I can see, I mean we can do something like Node(v, []), but what with v we at least have the root.


Solution

  • Your NTreeZipper.ntree type is abstract, so outside of the module there is no way to create a tree at all, and no, as you've correctly surmised, your tree type does not allow for an empty tree.

    As for creating a to_ntree function (assuming we have a way to create an ntree in the first place), the case where the context is Top is straightforward.

    let to_ntree = function
      | TZ (Top, t) -> t
    

    It's not much more complicated in the remaining case, which we can match on. You then are basically reversing the logic in from_ntree

    let to_ntree = function
      | TZ (Top, t) -> t
      | TZ (Context (ls, v, u, rs), t) -> ...