Discussion:
[Coq-Club] module "best practices"?
Kevin Hamlen
2018-08-17 20:36:01 UTC
Permalink
Dear Coq users,

What do Coq programmers consider the "right way" to organize large
projects containing functors?  In particular, how do authors of large
projects define collections of modules that all build upon some shared
functor without getting duplicate name-conflicts? Here's an example:

(* Input parameters and axioms are declared as a module type: *)
Module Type FOO_TYPE.
  Parameter foo: Type.
End FOO_TYPE.

(* Another module builds definitions from the inputs: *)
Module FooDefs (Foo: FOO_TYPE).
  Inductive bar := Bar (f:Foo.foo).
  (* ... hundreds more definitions ... *)
End FooDefs.

(* Now we wish to create myriad modules that prove various things about
FooDefs given a Foo.  Here's one way that doesn't work well: *)
Module FooTheory1 (Foo: FOO_TYPE).
  Module F := FooDefs Foo.
  Import F.
  Ltac silly1 := match goal with |- Bar _ = Bar _ => reflexivity end.
  (* ... more theorems, proofs, and ltacs ... *)
End FooTheory1.

(* This doesn't work well because each module defined in this way has
its own copy of FooDefs, which Coq doesn't always recognize as equal. *)
Module Foo:FOO_TYPE.
  Definition foo:=nat.
End Foo.
Module FD := FooDefs Foo.
Module FT1 := FooTheory1 Foo.
Import FD.
Import FT1.
Theorem silly: forall (x:bar), x=x.
Proof.
  destruct x.
  Fail silly1. (* fails because FD.Bar is not the same as FT1.F.Bar. *)
Abort.


Here are two alternative options that also don't seem to work well:

(2) Using "Export F" in FooTheory1 instead of creating a separate FD
module fixes the problem above, but then all FooTheory modules must be
arranged in a linear hierarchy to avoid duplicating the definition of
F.  They can't be created independently, lest they export two distinctly
named versions of F.

(3) We could make a FOO_DEFS module type (whose content is a verbatim
copy of FooDefs) and then parameterize all the FooTheory modules with
FOO_DEFS instead of FOO_TYPE modules.  But that leads to a rather
tortuous development regimen: Every new FooTheory module must also
define a FOO_THEORY type that duplicates all its definitions, and
instantiating the new FooTheory requires first laboriously instantiating
all the modules it depends upon, just so that they can all import the
shared definition of FooDefs.

What is the "right" way to do this sort of thing?

Thanks,
Kevin
Soegtrop, Michael
2018-08-18 11:06:33 UTC
Permalink
Dear Kevin,

I would say go with option 3) and use the <+ operator to combine modules and module types into larger ones in order to avoid the "tortuous development regimen with laboriously instantiating all the modules". I show below how this would look for your example and also show a little appendix D stuff.

In general this gets easier when you work more abstract. It is strange that most of your modules are living on their own without implementing any module type. If your developments look like this, it might be that you are using the module system just as a namespace system. The idea behind the module system is that you implement modules using only abstract definitions (what is in a module type) of other modules. If you generally depend on concrete implementations of other modules in module definitions, you end up with a lot of trouble of the kind you describe. If you mostly depend on what is in the module type in the definition of a module, things are much more natural. I usually work as abstract as possible, because this makes automation more efficient. If you have a lot of concrete details in the context you don't really need, automation will try to make use of it anyway rather than applying higher level abstract results, which should be sufficient. Especially simplification is not your friend if you work to concrete. Of cause this can get tricky, like working with abstract numbers, but things are improving. An important point with modules when going the abstract way is looking into opaque and transparent ascription and using "with x := y" in opaque ascriptions properly.

I agree that in complex developments there are corner cases between what should be a module type and what should be a module. In such cases I typically have both and use the Empty <+ trick shown below to make a module from a module type. Also building sensible collections of modules and module types using the <+ operator is a useful approach to simplify things.

I am generally interested in large scale maintainable proof engineering and try to understand what issues people end up with. If your project is open source, I would like to have a look at it (please send me a private mail in case).

Best regards,

Michael

Some ideas:

(* Input parameters and axioms are declared as a module type: *)
Module Type FOO_TYPE.
Parameter foo: Type.
End FOO_TYPE.

(* This might be used as both, a module and a module type, so define both *)
Module Type FOO_DEFS (Foo: FOO_TYPE).
Inductive bar := Bar (f:Foo.foo).
End FOO_DEFS.

(* Make a module from a module type *)
Module FooDefs (Foo: FOO_TYPE).
Include FOO_DEFS Foo.
End FooDefs.

(* There is an abbreviation for this.
Use an Empty module to trick the <+ include operator in accepting a module type.
The first operand of <+ must be a module in a module definition, but all others can be module tpes. *)

Module Empty.
End Empty.

Module FooDefs' (Foo: FOO_TYPE) := Empty <+ FOO_DEFS Foo.

(* Compare Print FooDefs and FooDefs' *)

(* Sometimes it is also useful to combine module types into one *)
Module Type FOO_WITH_DEFS := FOO_TYPE <+ FOO_DEFS.

(* Theory getting everything separately *)
Module FooTheory1 (Foo: FOO_TYPE) (F: FOO_DEFS Foo).
Import F.
Ltac silly1 := match goal with |- Bar _ = Bar _ => reflexivity end.
(* ... more theorems, proofs, and ltacs ... *)
End FooTheory1.

(* Theory getting all in one *)
Module FooTheory2 (F: FOO_WITH_DEFS).
Import F.
Ltac silly2 := match goal with |- Bar _ = Bar _ => reflexivity end.
(* ... more theorems, proofs, and ltacs ... *)
End FooTheory2.

(* This doesn't work well because each module defined in this way has its own copy of FooDefs, which Coq doesn't always recognize as equal. *)
Module Foo <: FOO_TYPE.
Definition foo:=nat.
End Foo.

Module FD := FooDefs Foo.
Module FT1 := FooTheory1 Foo FD.
(* The all in one argument for FooTheory2 is easily created *)
Module FDX := Foo <+ FD.
Module FT2 := FooTheory2 FDX.
Import FD.
Import FT1.
Import FT2.

(* Both theories work fine *)

Theorem silly1: forall (x:bar), x=x.
Proof.
destruct x.
silly1.
Qed.

Theorem silly2: forall (x:bar), x=x.
Proof.
destruct x.
silly2.
Qed.

Intel Deutschland GmbH
Registered Address: Am Campeon 10-12, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Christin Eisenschmid, Christian Lamprechter
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgeri
Samuel Gruetter
2018-08-18 14:52:22 UTC
Permalink
Some time ago, we were deciding whether to use modules or typeclasses as the
primary means of abstraction in bedrock2, and the problem you describe (or at
least, a similar one) seemed so unsolvable that we decided to use typeclasses
and not use modules at all (which I think is sad).

The corresponding discussion is here:
https://github.com/mit-plv/bedrock2/commit/20d5fac26f#r29468057

The problem I'm referring to is: If you put an Inductive inside a module
functor, and instantiate it twice with the same module, you get two
instantiations of the Inductive which Coq considers to be different types.

The only solution seemed to be to define the Inductive outside of the module
functor, but that defeats the point because we'd have to pass it the
parameters each time we use it, which is precisely what we're trying to
avoid.
Post by Kevin Hamlen
Dear Coq users,
What do Coq programmers consider the "right way" to organize large
projects containing functors? In particular, how do authors of large
projects define collections of modules that all build upon some shared
(* Input parameters and axioms are declared as a module type: *)
Module Type FOO_TYPE.
Parameter foo: Type.
End FOO_TYPE.
(* Another module builds definitions from the inputs: *)
Module FooDefs (Foo: FOO_TYPE).
Inductive bar := Bar (f:Foo.foo).
(* ... hundreds more definitions ... *)
End FooDefs.
(* Now we wish to create myriad modules that prove various things about
FooDefs given a Foo. Here's one way that doesn't work well: *)
Module FooTheory1 (Foo: FOO_TYPE).
Module F := FooDefs Foo.
Import F.
Ltac silly1 := match goal with |- Bar _ = Bar _ => reflexivity end.
(* ... more theorems, proofs, and ltacs ... *)
End FooTheory1.
(* This doesn't work well because each module defined in this way has
its own copy of FooDefs, which Coq doesn't always recognize as equal. *)
Module Foo:FOO_TYPE.
Definition foo:=nat.
End Foo.
Module FD := FooDefs Foo.
Module FT1 := FooTheory1 Foo.
Import FD.
Import FT1.
Theorem silly: forall (x:bar), x=x.
Proof.
destruct x.
Fail silly1. (* fails because FD.Bar is not the same as FT1.F.Bar. *)
Abort.
(2) Using "Export F" in FooTheory1 instead of creating a separate FD
module fixes the problem above, but then all FooTheory modules must be
arranged in a linear hierarchy to avoid duplicating the definition of
F. They can't be created independently, lest they export two distinctly
named versions of F.
(3) We could make a FOO_DEFS module type (whose content is a verbatim
copy of FooDefs) and then parameterize all the FooTheory modules with
FOO_DEFS instead of FOO_TYPE modules. But that leads to a rather
tortuous development regimen: Every new FooTheory module must also
define a FOO_THEORY type that duplicates all its definitions, and
instantiating the new FooTheory requires first laboriously instantiating
all the modules it depends upon, just so that they can all import the
shared definition of FooDefs.
What is the "right" way to do this sort of thing?
Than
Soegtrop, Michael
2018-08-19 09:15:07 UTC
Permalink
Dear Samuel,

I think I have shown how to fix this kind of issue in my post. I don't see how your case is different. I would suggest to try the following:

- put the inductive type definitions from module functor Imp into a module type functor IMP
- define module Imp as "Module Imp (P : ImpParameters) := Empty <+ IMP P."
- give everything which needs Imp a parameter of type IMP and instantiate it with Imp
- possibly define combined module types and modules to avoid long parameter lists

The trick is that now the inductive definition is in the module type rather than in the module instance and the type is the same for all instances, so the inductive types are the same. At the end of the code I posted, you can actually prove the following:

Theorem Eq1: FD.bar = FDX.bar.
Proof.
reflexivity.
Qed.

Theorem Eq2: FD.Bar = FDX.Bar.
Proof.
reflexivity.
Qed.

although Coq still thinks FD.bar and FDX.bar are separate definitions as Locate bar or Locate Bar shows.

This might seem like a bizarre hack, but I think it is quite logical to do it this way. My view is that whatever is needed by other modules must be defined in a module type. Modules are there to define concrete implementations of abstract definitions in a module type. Other modules should only use module types and not concrete modules. When you cannot abstract away from what is in a module, it is part of the interface and doesn't belong in a module but in a module type.

If you have an inductive definition and where you use this type you can replace it with an abstract type (say a generic map data structure), the inductive belongs in a module and the module type just provides an abstract type. If you need the details of the inductive in modules using it, the inductive belongs into a module type.

Of cause if a module A just uses another module B to implement its functionality in such a way that nothing of B is visible at the interface (signature) of A, A can just use a module B. This typically applies to modules just defining a bunch of lemmas, tactics or notations but also to modules defining helper data structures like maps or sets, which are abstracted away at the external interface of module A. But anything about other modules which is somehow visible in the signature of A, must be defined in a module type, and not in a module.

In my experience the module system works quite well, if it is used in this way.

I hope those who came up with the module system agree with this view.

Best regards,

Michael

Intel Deutschland GmbH
Registered Address: Am Campeon 10-12, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Christin Eisenschmid, Christian Lamprechter
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 1
Kevin Hamlen
2018-08-20 03:53:08 UTC
Permalink
Dear Michael,

Thank you for your suggestion; it's enormously helpful.  I agree that
it's the best solution I've seen.

As you pointed out, the only downside I've found so far is that one must
still be very careful about any Module Type that defines/imports a
Module (instead of a Module Type).  If any names from that Module leak
into theorems or ltacs exported by the Module Type, then those theorems
and ltacs sometimes aren't recognized by Coq as unifying with
differently-named terms created by equivalent but distinct instances of
the same Module.  But with sufficient care and avoidance of Modules in
favor of Module Types, I think your approach works.

To answer your questions:  Our use of modules is driven by the need to
parameterize our definitions by things that the Coq standard libraries
define as modules (e.g., in Coq.Structures.Equalities). So although the
module functor that receives one of these library-provided modules need
not have a module-type that allows multiple implementations, the module
it receives has a module-type and may indeed have various different
implementations.  This seems to create a domino effect---all module
functors in the inheritance hierarchy must apparently be implemented as
module-types instead of modules, and all instantiations of those
module-types are just dummy modules that include everything from the
module-type.

Since we're targeting our project to eventually be open-source and
hopefully valuable for general use, we were searching for an approach
that is "accepted" by others.  This seems like a good one.

Best regards,
Kevin
Post by Soegtrop, Michael
Dear Samuel,
- put the inductive type definitions from module functor Imp into a module type functor IMP
- define module Imp as "Module Imp (P : ImpParameters) := Empty <+ IMP P."
- give everything which needs Imp a parameter of type IMP and instantiate it with Imp
- possibly define combined module types and modules to avoid long parameter lists
Theorem Eq1: FD.bar = FDX.bar.
Proof.
reflexivity.
Qed.
Theorem Eq2: FD.Bar = FDX.Bar.
Proof.
reflexivity.
Qed.
although Coq still thinks FD.bar and FDX.bar are separate definitions as Locate bar or Locate Bar shows.
This might seem like a bizarre hack, but I think it is quite logical to do it this way. My view is that whatever is needed by other modules must be defined in a module type. Modules are there to define concrete implementations of abstract definitions in a module type. Other modules should only use module types and not concrete modules. When you cannot abstract away from what is in a module, it is part of the interface and doesn't belong in a module but in a module type.
If you have an inductive definition and where you use this type you can replace it with an abstract type (say a generic map data structure), the inductive belongs in a module and the module type just provides an abstract type. If you need the details of the inductive in modules using it, the inductive belongs into a module type.
Of cause if a module A just uses another module B to implement its functionality in such a way that nothing of B is visible at the interface (signature) of A, A can just use a module B. This typically applies to modules just defining a bunch of lemmas, tactics or notations but also to modules defining helper data structures like maps or sets, which are abstracted away at the external interface of module A. But anything about other modules which is somehow visible in the signature of A, must be defined in a module type, and not in a module.
In my experience the module system works quite well, if it is used in this way.
I hope those who came up with the module system agree with this view.
Best regards,
Michael
Intel Deutschland GmbH
Registered Address: Am Campeon 10-12, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Christin Eisenschmid, Christian Lamprechter
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
Soegtrop, Michael
2018-08-20 07:17:57 UTC
Permalink
Dear Kevin,

I agree that some decisions in the standard library on what are modules and what are module types, what are functors and what not and how component modules/module types are combined to larger entities are suboptimal. Actually I repackaged many of the standard library modules, e.g. on orders, in a different way. Afaik Maxime started to work on a new version of the library - we should make this a good example on how modules should be used and add some explanations on the rationale. In my experience if one keeps the "module interfaces belong in module types" idea in mind, things work out quite naturally and unproblematic.

As I said, if you have specific issues, I would like to look over them and see if I can fix them or also review your usage of modules. Maybe I write some pages on the topic from a users / software engineering perspective and for this it would help to understand what issues other people have.

Best regards,

Michael
Intel Deutschland GmbH
Registered Address: Am Campeon 10-12, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Christin Eisenschmid, Christian Lamprechter
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsg
Benjamin C. Pierce
2018-08-20 17:43:05 UTC
Permalink
I would love to see (an expanded version of) this discussion written down carefully somewhere!!

- B
Post by Soegtrop, Michael
Dear Kevin,
I agree that some decisions in the standard library on what are modules and what are module types, what are functors and what not and how component modules/module types are combined to larger entities are suboptimal. Actually I repackaged many of the standard library modules, e.g. on orders, in a different way. Afaik Maxime started to work on a new version of the library - we should make this a good example on how modules should be used and add some explanations on the rationale. In my experience if one keeps the "module interfaces belong in module types" idea in mind, things work out quite naturally and unproblematic.
As I said, if you have specific issues, I would like to look over them and see if I can fix them or also review your usage of modules. Maybe I write some pages on the topic from a users / software engineering perspective and for this it would help to understand what issues other people have.
Best regards,
Michael
Intel Deutschland GmbH
Registered Address: Am Campeon 10-12, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Christin Eisenschmid, Christian Lamprechter
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
Soegtrop, Michael
2018-08-23 10:54:12 UTC
Permalink
Dear Benjamin,

I just saw that my answer didn't make it through the mail server - it contained an image and was too large.

I wanted to write something down 2016, but I ran into severe issues with my samples with the module structure of the standard library. The example was about maps implemented as ordered tress and using a module functor to extend a given ordered key module with a plus and minus infinity element (and the correspondingly extended order). This makes the specification of subtree key ranges easier, because one can use infinity as range for the whole tree. Doing this, I ended up rewriting half of the order modules of the standard library.

I think it is really difficult to come up with a good module system tutorial when the methods advocated in there are incompatible with the standard library. But it might be that I overdid things 2 years back. If it is really as bad as I concluded back then, I guess it would be best to rework the standard library and make this serve as a good example.

Something I would like to discuss is an appropriate naming scheme for related modules, module types, module functors, module type functors, aggregated modules, module types, module functors and module type functors, modules providing notations and those containing notations, module types requesting functions, module types specifying such functions, modules implementing such functions and modules showing that the specifications hold, module types just wrapping type classes and the type class name and a few others.

My original post contained an image of how this is done in the standard library for orders - it didn't get through I will post it somewhere and send the link after I reviewed my rewrites of this.

Best regards,

Michael
Intel Deutschland GmbH
Registered Address: Am Campeon 10-12, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Christin Eisenschmid, Christian Lamprechter
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
Adam Chlipala
2018-08-21 17:58:25 UTC
Permalink
Post by Kevin Hamlen
What do Coq programmers consider the "right way" to organize large
projects containing functors?  In particular, how do authors of large
projects define collections of modules that all build upon some shared
functor without getting duplicate name-conflicts?
It really seems like a distraction to work so hard to put an inductive
definition directly inside a functor body.  I would do it like this:

Module Type FOO_TYPE.
  Parameter foo: Type.
End FOO_TYPE.

Inductive bar T := Bar (f : T).

Module FooDefs (Foo: FOO_TYPE).
  Definition bar := bar Foo.foo.
  (* ... hundreds more definitions ... *)
End FooDefs.

Module FooTheory1 (Foo: FOO_TYPE).
  Module Import F := FooDefs Foo.
  Ltac silly1 := match goal with |- Bar _ _ = Bar _ _ => reflexivity end.
  (* ... more theorems, proofs, and ltacs ... *)
End FooTheory1.

Module Foo:FOO_TYPE.
  Definition foo:=nat.
End Foo.
Module Import FD := FooDefs Foo.
Module Import FT1 := FooTheory1 Foo.
Theorem silly: forall (x:bar), x=x.
Proof.
  destruct x.
  silly1.
Qed.
Kevin Hamlen
2018-08-21 18:50:47 UTC
Permalink
Hi Adam,

Yes, pulling the Inductive definitions out and parameterizing them with
type variables is possible.  But it can lead to unpleasantly large
parameter lists and huge proof contexts.  For example, if FOO_TYPE
contains 20 parameters, each of which appear individually in
constructors of bar, then we're stuck writing 19 extra arguments to each
bar-constructor.  It gets worse when FOO_TYPE contains Axioms that leak
into inductive types as  dependently typed parameters.  We could, of
course, create specialized versions of all the constructors (e.g.,
barfoo := Bar Foo.foo), but then we get back into the original problem:
distinct but equivalent instantiations of FooTheory1 Foo create separate
versions of barfoo that don't always unify easily.

What we really want, I think, is a single, centralized definition of
Foo.foo that all the FooTheory's share, so that there are never any
duplicate names floating around.  That seems to jive with Michael's
solution: Functors that want to share a definition of Foo.foo should
incorporate that definition as part of the Module Type of their module
parameters, so that it becomes part of their interfaces.

--Kevin
Post by Adam Chlipala
Post by Kevin Hamlen
What do Coq programmers consider the "right way" to organize large
projects containing functors?  In particular, how do authors of large
projects define collections of modules that all build upon some
shared functor without getting duplicate name-conflicts?
It really seems like a distraction to work so hard to put an inductive
Module Type FOO_TYPE.
  Parameter foo: Type.
End FOO_TYPE.
Inductive bar T := Bar (f : T).
Module FooDefs (Foo: FOO_TYPE).
  Definition bar := bar Foo.foo.
  (* ... hundreds more definitions ... *)
End FooDefs.
Module FooTheory1 (Foo: FOO_TYPE).
  Module Import F := FooDefs Foo.
  Ltac silly1 := match goal with |- Bar _ _ = Bar _ _ => reflexivity end.
  (* ... more theorems, proofs, and ltacs ... *)
End FooTheory1.
Module Foo:FOO_TYPE.
  Definition foo:=nat.
End Foo.
Module Import FD := FooDefs Foo.
Module Import FT1 := FooTheory1 Foo.
Theorem silly: forall (x:bar), x=x.
Proof.
  destruct x.
  silly1.
Qed.
Samuel Gruetter
2018-08-22 19:47:30 UTC
Permalink
The fact that a module signature can not only contain the signature
of an inductive or of a function, but also its body, was new to me,
thanks for explaining, Michael!

To compare the approach of putting Inductives outside modules (as
advocated by Adam) and the approach of putting them inside a
module type functor (as advocated by Michael), I wrote a somewhat
longer example (apologies if it's too long...).

You can search for comments beginning with "Quirk" to find spots
where I'm not satisified with the solution, and spots where a
solution does better than the other are marked with "NoQuirk",
while questions are marked with "Question".

Overall it seems that putting the Inductives inside a module type
functor has much fewer quirks, but requires a somewhat heretical
feature not present in Standard ML's module system, namely to
"Include" a signature inside a module; it seems to me that this
mixes two worlds which should be separate.

Below follows some Coq code which can be pasted directly into one
file, but it actually represents several files.
The "APPROACH_" and "FILE_" prefixes are just to keep different
approaches in separate namespaces, and to mark what would go into
separate files, but these prefixes would not show up in an actual
development.


(* ------------------------------------------------------------------ *)

Require Import Coq.omega.Omega.

Module APPROACH_Inductive_outside_Module.

Module FILE_MyLang.
Section Expression.

Variable word: Set.
Variable varname: Set.
Variable bopname: Set.

Context {varname_eq_dec: forall (x y: varname), {x = y} + {x <> y}}.
Context {bopname_eq_dec: forall (x y: bopname), {x = y} + {x <> y}}.

Inductive expr0: Set :=
| ELit0(v: word)
| EVar0(x: varname)
| EOp0(op: bopname)(e1 e: expr0).

End Expression.

Module Type PARAMETERS.
Parameter word: Set.
Parameter varname: Set.
Parameter bopname: Set.
Axiom varname_eq_dec: forall (x y: varname), {x = y} + {x <> y}.
Axiom bopname_eq_dec: forall (x y: bopname), {x = y} + {x <> y}.
End PARAMETERS.

Module Lang(P: PARAMETERS).
Import P.

(* Quirk: Here's some "once and for all" boilerplate.
Quesion: Can I get rid of this? *)
Definition expr: Set := expr0 word varname bopname.
Definition ELit: word -> expr :=
ELit0 word varname bopname.
Definition EVar: varname -> expr :=
EVar0 word varname bopname.
Definition EOp: bopname -> expr -> expr -> expr :=
EOp0 word varname bopname.

(* Quirk: More boilerplate: To match on an expr, we don't want
to match on the type arguments, so let's make them implicit.
Question: Can we do the above with Notations and reuse
the notations for pattern matching?
(I couldn't get it to work): *)
Arguments ELit0 {_} {_} {_}.
Arguments EVar0 {_} {_} {_}.
Arguments EOp0 {_} {_} {_}.

(* Quirk/Question: Can we use ELit instead of ELit0 here? *)
Fixpoint expr_size(e: expr): nat :=
match e with
| ELit0 _ => 1
| EVar0 _ => 1
| EOp0 _ e1 e2 => S (expr_size e1 + expr_size e2)
end.
End Lang.
End FILE_MyLang.

Module FILE_LibSize.
Import FILE_MyLang.

Module LibSize(P: PARAMETERS).
Module Import LangInst := Lang P.

Lemma expr_size_pos: forall (e: expr), expr_size e > 0.
Proof. induction e; simpl; omega. Qed.

End LibSize.
End FILE_LibSize.

Module FILE_LibSizeAlt.
Import FILE_MyLang.
Import FILE_LibSize.

Module LibSizeAlt(P: PARAMETERS).
Module Import LangInst := Lang P.
Module LibSizeInst := LibSize P.

Lemma expr_size_pos_alt: forall (e: expr), 0 < expr_size e.
Proof.
intros.
pose proof (LibSizeInst.expr_size_pos e).
Fail omega.
(* Quirk/Question: do I really have to do this? *)
change LibSizeInst.LangInst.expr_size with expr_size in H.
omega.
Qed.

End LibSizeAlt.
End FILE_LibSizeAlt.

Module FILE_NatNameLemmas.
Import FILE_MyLang.

Module Type PARAMETERS_NAT_NAMES <: PARAMETERS.
Parameter word: Set.
Definition varname := nat.
Definition bopname := nat.
Definition varname_eq_dec := Nat.eq_dec.
Definition bopname_eq_dec := Nat.eq_dec.
End PARAMETERS_NAT_NAMES.

Module NatNameLemmas(P: PARAMETERS_NAT_NAMES).
(* module subtyping at work: *)
Module Import LangInst := Lang P.

Fixpoint meaningless_sum(e: expr): nat :=
match e with
| ELit0 n => 0
| EVar0 x => x
| EOp0 op e1 e2 => op + meaningless_sum e1 + meaningless_sum e2
end.
End NatNameLemmas.
End FILE_NatNameLemmas.

Module FILE_CombineDifferentSpecializationLevels.
Import FILE_MyLang.
Import FILE_LibSizeAlt.
Import FILE_NatNameLemmas.

Module Combination(P: PARAMETERS_NAT_NAMES).
(* Quirk/Question: Can we avoid having one module instantiation
line for each file imported above? *)
Module Import LangInst := Lang P.
Module Import LibSizeAltInst := LibSizeAlt P.
Module Import NatNameLemmasInst := NatNameLemmas P.

Definition silly(e: expr): nat := expr_size e + meaningless_sum e.

Lemma silly_pos: forall e, 0 < silly e.
Proof.
intros. unfold silly.
pose proof (expr_size_pos_alt e).
Fail omega.
change LibSizeAltInst.LangInst.expr_size with expr_size in *.
omega.
Qed.
End Combination.
End FILE_CombineDifferentSpecializationLevels.

Module FILE_AllConcrete.
Import FILE_MyLang.
Import FILE_NatNameLemmas.
Import FILE_CombineDifferentSpecializationLevels.

(* Note: We do not do this:
Module Import MyParameters: PARAMETERS_NAT_NAMES.
Because if we did, "word" would be opaque in the remainder of this
file. Instead, we use transparent ascription: *)
Module Import MyParameters <: PARAMETERS_NAT_NAMES.

Definition word := { x: nat | 0 <= x < 256 }.

(* Quirk/Question: Why do I have to repeat all these definitions?
There's nothing unknown or unspecified. *)
Definition varname := nat.
Definition bopname := nat.
Definition varname_eq_dec := Nat.eq_dec.
Definition bopname_eq_dec := Nat.eq_dec.
End MyParameters.

Module Import LangInst := Lang MyParameters.
Module Import CombinationInst := Combination MyParameters.

Definition word_two: word.
refine (exist _ 2 _); abstract omega.
Defined.

Definition test_expr: nat := silly (EOp 3 (ELit word_two) (EVar 5)).

Eval cbv in test_expr.

Lemma test_expr_pos: 0 < test_expr.
Proof. apply silly_pos. Qed.

End FILE_AllConcrete.

End APPROACH_Inductive_outside_Module.

(* ------------------------------------------------------------------ *)

Module APPROACH_Inductive_inside_Module_Type.

Module FILE_MyLang.

Module Type PARAMETERS.
Parameter word: Set.
Parameter varname: Set.
Parameter bopname: Set.
Axiom varname_eq_dec: forall (x y: varname), {x = y} + {x <> y}.
Axiom bopname_eq_dec: forall (x y: bopname), {x = y} + {x <> y}.
End PARAMETERS.

Module Type LANG(P: PARAMETERS).
Import P.

Inductive expr: Set :=
| ELit(v: word)
| EVar(x: varname)
| EOp(op: bopname)(e1 e: expr).

(* Other modules will depend on the implementation of
this, therefore we put it in the signature *)
Fixpoint expr_size(e: expr): nat :=
match e with
| ELit _ => 1
| EVar _ => 1
| EOp _ e1 e2 => S (expr_size e1 + expr_size e2)
end.
End LANG.

Module Lang(P: PARAMETERS): LANG P.
Include (LANG P).
End Lang.
End FILE_MyLang.

Module FILE_LibSize.
Import FILE_MyLang.

(* Here we also parameterize over LANG rather than over
PARAMETERS only, because we should not instantiate Lang several
times, because if we do, we get different copies of expr. *)
Module LibSize(P: PARAMETERS)(L: LANG P).
Import P.
Import L.
(* Question: We could also have declared "L: Lang", and then do
Module Import LInst := L P.
instead of "Import L". What's the difference? *)

Lemma expr_size_pos: forall (e: expr), expr_size e > 0.
Proof. induction e; simpl; omega. Qed.

End LibSize.
End FILE_LibSize.

Module FILE_LibSizeAlt.
Import FILE_MyLang.
Import FILE_LibSize.

Module LibSizeAlt(P: PARAMETERS)(L: LANG P).
Import P.
Import L.
Module Import LibSizeInst := LibSize P L.

Lemma expr_size_pos_alt: forall (e: expr), 0 < expr_size e.
Proof.
intros.
pose proof (expr_size_pos e).
(* NoQuirk:
Here, omega works directly, without "change" before. *)
omega.
Qed.

End LibSizeAlt.
End FILE_LibSizeAlt.

Module FILE_NatNameLemmas.
Import FILE_MyLang.

Module Type PARAMETERS_NAT_NAMES <: PARAMETERS.
Parameter word: Set.
Definition varname := nat.
Definition bopname := nat.
Definition varname_eq_dec := Nat.eq_dec.
Definition bopname_eq_dec := Nat.eq_dec.
End PARAMETERS_NAT_NAMES.

Module NatNameLemmas(P: PARAMETERS_NAT_NAMES)(L: LANG P).
Import P.
Import L.

(* NoQuirk: Here we can use ELit instead of ELit0 etc *)
Fixpoint meaningless_sum(e: expr): nat :=
match e with
| ELit n => 0
| EVar x => x
| EOp op e1 e2 => op + meaningless_sum e1 + meaningless_sum e2
end.
End NatNameLemmas.
End FILE_NatNameLemmas.

Module FILE_CombineDifferentSpecializationLevels.
Import FILE_MyLang.
Import FILE_LibSizeAlt.
Import FILE_NatNameLemmas.

Module Combination(P: PARAMETERS_NAT_NAMES)(L: LANG P).
Import P.
(* Quirk/Question: Can we avoid having one module instantiation
line for each file imported above? *)
Import L.
Module Import LibSizeAltInst := LibSizeAlt P L.
Module Import NatNameLemmasInst := NatNameLemmas P L.

Definition silly(e: expr): nat := expr_size e + meaningless_sum e.

Lemma silly_pos: forall e, 0 < silly e.
Proof.
intros. unfold silly.
pose proof (expr_size_pos_alt e).
(* NoQuirk: omega works directly *)
omega.
Qed.
End Combination.
End FILE_CombineDifferentSpecializationLevels.

Module FILE_AllConcrete.
Import FILE_MyLang.
Import FILE_NatNameLemmas.
Import FILE_CombineDifferentSpecializationLevels.

(* Note: We do not do this:
Module Import MyParameters: PARAMETERS_NAT_NAMES.
Because if we did, "word" would be opaque in the remainder of this
file. Instead, we use transparent ascription: *)
Module Import MyParameters <: PARAMETERS_NAT_NAMES.

Definition word := { x: nat | 0 <= x < 256 }.

(* Quirk/Question: Why do I have to repeat all these definitions?
There's nothing unknown or unspecified. *)
Definition varname := nat.
Definition bopname := nat.
Definition varname_eq_dec := Nat.eq_dec.
Definition bopname_eq_dec := Nat.eq_dec.
End MyParameters.

Module Import LangInst := Lang MyParameters.
Module Import CombinationInst := Combination MyParameters LangInst.

Definition word_two: word.
refine (exist _ 2 _); abstract omega.
Defined.

Definition test_expr: nat := silly (EOp 3 (ELit word_two) (EVar 5)).

Eval cbv in test_expr.

Lemma test_expr_pos: 0 < test_expr.
Proof. apply silly_pos. Qed.

End FILE_AllConcrete.

End APPROACH_Inductive_inside_Module_Type.

(* -----------------------------------------
Soegtrop, Michael
2018-08-23 10:21:24 UTC
Permalink
Dear Samuel,

thanks for working this out! One question: from the link you sent before I guess that you also experimented with records instead of modules. Could you comment on your experience?

I agree that including module types in modules is a strange concept, but since it supports the idea of putting the interface of a module into a module type, I wouldn't call it heretic ;-) What would be cleaner is if a module is declared to have some module type, that then it includes all definitions from the module type automatically (both with transparent and opaque ascription, since it is part of the interface). This is how I mentally interpret what I am doing, even though I do the import explicitly.

Regarding your questions in the "module type functor method section":

(* Question: We could also have declared "L: Lang", and then do
Module Import LInst := L P.
instead of "Import L". What's the difference? *)

I think it is the same (but I didn't check the module typing rules). I would do the partial module type functor application of LANG only if P would not be an argument of the module but be a module which is instantiated in the body of the module. That is if P would not be part of the interface of the module, but an abstractable implementation detail. Such things do happen in practice. But in this case I would clearly say L : LANG P.

(* Quirk/Question: Can we avoid having one module instantiation

Yes, you can write:

Module Combination(Import P: PARAMETERS_NAT_NAMES)(Import L: LANG P).

(* Quirk/Question: Why do I have to repeat all these definitions?
There's nothing unknown or unspecified. *)

As I wrote before, it would be nice if the module type ascription would automatically include definitions. This would solve this.

Following my rules the definitions wouldn't belong in a module type, since they are not part of a non abstractable module interface. What happens is that by specialization you split a module type into a part which should still be a module type and a part which should be a module. If I have such cases, I split them up in two module types, so that by specialization one can stay a module type and the other can become a module.

I have to think about if there is a more elegant solution.

Maybe we should do a feature request to have the "include definitions in module type ascription". I guess not that many people are using definitions in module types, so likely it would break little code.

Best regards,

Michael
Intel Deutschland GmbH
Registered Address: Am Campeon 10-12, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Christin Eisenschmid, Christian Lamprechter
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial
David Holland
2018-08-23 20:43:28 UTC
Permalink
Post by Soegtrop, Michael
Maybe we should do a feature request to have the "include
definitions in module type ascription". I guess not that many
people are using definitions in module types, so likely it would
break little code.
+1

It's always irritated me in ML that if you put anything nontrivial in
an interface you have to cutpaste it into every implementation.
Especially because it seems completely unnecessary; but maybe there's
some subtlety I'm missing about uniqueness of possible
concretizations.

(as far as breaking stuff, one can just add new syntax for the new form)
--
David A. Holland
***@netbsd.org
Anders Lundstedt
2018-08-24 13:47:29 UTC
Permalink
Post by Soegtrop, Michael
Maybe we should do a feature request to have the "include
definitions in module type ascription". I guess not that many
people are using definitions in module types, so likely it would
break little code.
Yes please!

Some previous discussion:
https://sympa.inria.fr/sympa/arc/coq-club/2017-10/msg00036.html
Soegtrop, Michael
2018-08-24 14:49:19 UTC
Permalink
Dear David, Anders, Samuel,
Maybe we should do a feature request to have the "include > definitions in
module type ascription". I guess not that many > people are using definitions in
module types, so likely it would > break little code.
Yes please!
I created the feature request: https://github.com/coq/coq/issues/8317

With this all "quirks" from Samuel's post would be solved.

Best regards,

Michael

Intel Deutschland GmbH
Registered Address: Am Campeon 10-12, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de
Managing Directors: Christin Eisenschmid, Christian Lamprechter
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenche

Loading...