caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
* [Caml-list] avoiding boilerplate with phantom types
@ 2012-11-10  0:02 Milan Stanojević
  2012-11-10  7:49 ` Gabriel Scherer
  0 siblings, 1 reply; 2+ messages in thread
From: Milan Stanojević @ 2012-11-10  0:02 UTC (permalink / raw)
  To: Caml List

This is my use case.
I have a module A with type t and bunch of functions. I want to create
module B which is just a wrapper around A with a phantom type
Roughly
module A : sig
  type t
  val foo : t -> int
  val bar : t -> string
end

module B : sig
   type phantom1
   type phantom2
   type 'a t   (* or maybe even type 'a t = private A.t *)
   val foo : _ t -> int
   val bar : _ t -> string

   val validate : phantom1 t -> phantom2 t
end

I was hoping I can avoid copy-pasting declarations and that I can have
some easy way to have A and B in sync as I add or remove functions
from A.
I was able to do something for implementation B but not for interface.

B.ml
type this_name_is_not_in_scope = A.t
include (A : module type of A with type t = this_name_is_not_in_scope)

type 'a t = A.t
type phantom1
type phantom2
let validate = ....

But for interface I can't do anything unless I expose type equality
between 'a B.t and A.t (which renders phantom types useless) so I had
to list all the functions by hand.
Does anyone have a better way to do this?

The problem is that I can't say include module type of A with type t := 'a t
Why is this disallowed?

Thanks,
   Milan

^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: [Caml-list] avoiding boilerplate with phantom types
  2012-11-10  0:02 [Caml-list] avoiding boilerplate with phantom types Milan Stanojević
@ 2012-11-10  7:49 ` Gabriel Scherer
  0 siblings, 0 replies; 2+ messages in thread
From: Gabriel Scherer @ 2012-11-10  7:49 UTC (permalink / raw)
  To: Milan Stanojević; +Cc: Caml List

That doesn't directly answer your question, but nowadays with GADTs it
is possible to start first from the richly typed declaration (what
corresponds to your "phantom type" one, but replaced by real type
checks instead of hopeful signature coercion), and define the one
without this type information as an existential wrapper on top of it.

(* rich statically typed module *)
module B = struct
  type 'a t =
    | Unit : unit t
    | Int : int -> int t
    | Bool : bool -> bool t

  let foo : type a . a t -> int option = function
    | Int n -> Some n
    | _ -> None

  let bar : type a . a t -> bool option = function
    | Bool b -> Some b
    | _ -> None

  let baz n = Int n
end

(* dynamic interface without static type knowledge *)
module A = struct
  type a = Ex : 'a B.t -> a
  let foo (Ex b) = B.foo b
  let bar (Ex b) = B.bar b
  let baz n = Ex (B.baz n)
end

If I was to do something like this, I wouldn't directly reproduce the
module A with the same interface as B, but rather just wait in the
user code for a need to pack or unpack (foo B.t) into a, and do it
there -- so A is merely an example of doing that. The upside of this
is that it translates your intention without requesting duplication.
The downside is that GADT type checks are more complex than just usual
algebraic types (with or without phantom types) and the quality of
error messages is still pretty experimental.

So, not necessarily the right choice right now (but if you're moving
into phantom types you're somehow already asking for trouble), but
something to think about.

On Sat, Nov 10, 2012 at 1:02 AM, Milan Stanojević <milanst@gmail.com> wrote:
> This is my use case.
> I have a module A with type t and bunch of functions. I want to create
> module B which is just a wrapper around A with a phantom type
> Roughly
> module A : sig
>   type t
>   val foo : t -> int
>   val bar : t -> string
> end
>
> module B : sig
>    type phantom1
>    type phantom2
>    type 'a t   (* or maybe even type 'a t = private A.t *)
>    val foo : _ t -> int
>    val bar : _ t -> string
>
>    val validate : phantom1 t -> phantom2 t
> end
>
> I was hoping I can avoid copy-pasting declarations and that I can have
> some easy way to have A and B in sync as I add or remove functions
> from A.
> I was able to do something for implementation B but not for interface.
>
> B.ml
> type this_name_is_not_in_scope = A.t
> include (A : module type of A with type t = this_name_is_not_in_scope)
>
> type 'a t = A.t
> type phantom1
> type phantom2
> let validate = ....
>
> But for interface I can't do anything unless I expose type equality
> between 'a B.t and A.t (which renders phantom types useless) so I had
> to list all the functions by hand.
> Does anyone have a better way to do this?
>
> The problem is that I can't say include module type of A with type t := 'a t
> Why is this disallowed?
>
> Thanks,
>    Milan
>
> --
> Caml-list mailing list.  Subscription management and archives:
> https://sympa.inria.fr/sympa/arc/caml-list
> Beginner's list: http://groups.yahoo.com/group/ocaml_beginners
> Bug reports: http://caml.inria.fr/bin/caml-bugs

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2012-11-10  7:49 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-11-10  0:02 [Caml-list] avoiding boilerplate with phantom types Milan Stanojević
2012-11-10  7:49 ` Gabriel Scherer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).