Search code examples
functional-programmingocaml

Accessing module defined operation such as: type 'a zipper = LZ of 'a list * 'a list


Let's suppose we have a module defined like this:

module type LIST_ZIPPER = 
  sig
    type 'a zipper
    exception First
    exception Last
    exception Empty
    val empty : 'a zipper
    (* returns the sublist starting at focus position*)
    val get : 'a zipper -> 'a list
    (* add/remove under focus operation *)
    val insert : 'a zipper -> 'a -> 'a zipper
    val remove : 'a zipper -> 'a zipper option
    val remove_exn : 'a zipper -> 'a zipper 
    (* focus-moving operation *)
    val previous : 'a zipper -> 'a zipper 
    val next : 'a zipper -> 'a zipper 
end

module ListZipper2 : LIST_ZIPPER = 
  struct
    type 'a zipper = LZ of 'a list * 'a list
    exception First
    exception Last
    exception Empty
    let empty : 'a zipper = LZ ([],[])
    let get (LZ (_,l) : 'a zipper) : 'a list = l
    let insert (LZ (l, r)) a = LZ (l,a::r)
    let remove lz = 
      match lz with
    | LZ (l, h::t) -> Some (LZ (l,t))
    | LZ (_,[]) -> None
    let remove_exn (lz : 'a zipper) : 'a zipper = 
      match remove lz with
      | Some lz -> lz
      | None -> raise Empty
    let previous lz =
       match lz with
    | LZ (h::t, l) -> LZ (t, h::l)
    | LZ ([],_) -> raise First
    let next lz = 
      match lz with
    | LZ (l, h::t) -> LZ (h::l,t)
    | LZ (_,[]) -> raise Last
  end

and I want to to create a zipper like this:

open ListZipper2
let (zip : ListZipper2.zipper) =  LZ ([1;2;3], [4;5])

and it doesn't work, what is the proper way? I can access operations defined such as ListZipper2.insert, but if I can't create a zipper, then what is the benefit of module in this case?


Solution

  • Your module type LIST_ZIPPER most probably defines 'a zipper as an abstract type:

    module type LIST_ZIPPER = sig
      type 'a zipper
      ...
    end
    

    Once you have restricted an implementation to this module type with

    module ListZipper2 : LIST_ZIPPER = struct
      ...
    end
    

    you can no longer peek inside the implementation detail of the module. This is by design since it enforces that the rest of the code will work with any LIST_ZIPPER independently of its contingent implementation.

    Note that this requires the LIST_ZIPPER module type to support enough operations for subsequent users to use the zipper. If you defined the LIST_ZIPPER module type yourself, it may happen that the module resulting from the signature constraint is unusable. For instance, with

    module type group = sig
      type t
      val (+): t -> t -> t
    end
    module Int: group = struct
      type t
      let (+) = (+)
    end
    

    the resulting module is unusable because there is no way to construct a value of type t exposed in the module type group.