Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to re-export a variant definition with renamed constructors? #7102

Closed
vicuna opened this issue Dec 18, 2015 · 5 comments
Closed

Ability to re-export a variant definition with renamed constructors? #7102

vicuna opened this issue Dec 18, 2015 · 5 comments

Comments

@vicuna
Copy link

vicuna commented Dec 18, 2015

Original bug ID: 7102
Reporter: @gasche
Status: acknowledged (set by @damiendoligez on 2017-04-13T11:18:24Z)
Resolution: open
Priority: normal
Severity: feature
Category: typing
Monitored by: @diml @hcarty

Bug description

We have a bit of an issue in Batteries for the 4.03 transition, as 4.03+dev includes a ('a result) type defined as

type ('a,'b) result = Ok of 'a | Error of 'b

while Batteries has distributed forever a datatype

type ('a, 'b) result = Ok  of 'a | Bad of 'b

This should, of course, not be blocking for the addition of a result type in 4.02.3, but it would be nice if it were possible to provide a smooth transition path to our users. (We can provide both datatypes, using type-directed constructor disambiguation, but there is still the issue that a number of functions return values of BatPervasives.result type, and we'd rather not duplicate them to have versions returning Pervasives.result).

Would it be possible to declare an alias with constructors of the same structure, but different names?

  type ('a, 'b) result = ('a, 'b) Pervasives.result =
    | Ok of 'a
    | Bad of 'b
@vicuna
Copy link
Author

vicuna commented Dec 18, 2015

Comment author: @alainfrisch

If we allow to rename constructors, this should be made explicit, not relying on the ordering of constructors.

E.g.

 type ('a, 'b) result = ('a, 'b) Pervasives.result =
    | Ok of 'a
    | Error as Bad of 'b

@vicuna
Copy link
Author

vicuna commented Dec 21, 2015

Comment author: @garrigue

Unfortunately, I'm afraid this feature first requires some theoretical work.

A first problem is that it breaks the type-erasure semantics of the language: now two constructors are equal if they have the same type (otherwise you cannot compare them) and same name. If they are allowed to have different names, then constructors are something else.

A second problem, which is a consequence of the first, is the interaction with constructor disambiguation. Suppose that you define

  type t = A | B

in some module M1, and re-export it as

  type u = M1.t = A as C | B as D
  type v = M1.t = A as D | B as C

Then you have in the same scope two constructors C and D, which belong to the same type, and whose resolution is ambiguous...

Maybe there is a proper way to do that, but this requires some fundamental work.
Moreover, since this changes the semantics, I would see this as a change to the language itself (not just a comfort improvement, like constructor/field disambiguation or inline records).

@vicuna
Copy link
Author

vicuna commented Dec 21, 2015

Comment author: @gasche

This is probably due to my very naive point of view, but I don't see
so many difficulties.

A first problem is that it breaks the type-erasure semantics of the
language: now two constructors are equal if they have the same type
(otherwise you cannot compare them) and same name. If they are allowed
to have different names, then constructors are something else.

I understand the feature as the ability to rebind constructors to
a different name while preserving equality information (in fact using
Alain's explicit syntax one could even rebind a constructor inside its
own declarations: type 'a option = None | Nothing as None | Some of
'a). This means that we need a resolution environment for constructors
(canonicalization/rewriting of constructors), but it is not
incompatible with a type-erased semantics:

  • you can perform resolution statically as an elaboration pass,
    replacing occurences of Bad by Error (for example during
    type-checking), which is not in the spirit of type-erasure; but

  • you can also maintain a runtime environment of constructor
    definitions, to be used at construction time (Bad v -> Error v)
    and dually at pattern-matching time (Error p -> Bad p).

The elaboration semantics is just a partial-evaluation optimization of
the type-erased runtime semantics, where those rewriting are done
statically instead of at runtime.

A second problem, which is a consequence of the first, is the interaction with constructor disambiguation. Suppose that you define

 type t = A | B

in some module M1, and re-export it as

 type u = M1.t = A as C | B as D
 type v = M1.t = A as D | B as C

Then you have in the same scope two constructors C and D, which belong to the same type, and whose resolution is ambiguous...

This situation can be rejected at declaration-time (or lazily whenever
C needs to be resolved), because the canonicalization environment for
constructor names is inconsistent (C is bound to two
incompatible constructors). If we had designed the feature before
type-directed resolution, we would have probably adopted a shadowing
semantics, but now that all constructors are "in scope" from their
type we shall accordingly guarantee that all their bindings are consistent.

Maybe there is a proper way to do that, but this requires some
fundamental work. Moreover, since this changes the semantics,
I would see this as a change to the language itself (not just
a comfort improvement, like constructor/field disambiguation or
inline records).

Ok, we'll have to find another solution then.

@vicuna
Copy link
Author

vicuna commented Sep 6, 2018

Comment author: @garrigue

Looking back at this discussion, Alain's approach could work if it is understood as just adding nicknames to constructors. I.e., there should be no way to hide the original name in the declaration, as this would break soundness for GADTs (it assumes that two variant types are distinct if their constructors are distinct).

This said, it still means that evaluation of an expression cannot be understood independently of the type definition, which I see as a downside (this is what I meant by type erasure, and elaboration is precisely what I want to avoid).

And the problem with disambiguation cannot be checked at definition time: you could have two conflicting redefinitions in different modules. This means that the meaning of a disambiguated expression may depend on the scope (i.e. which one is open) whereas the type is really the same. And this breaks your "interpretation" semantics.

@github-actions
Copy link

github-actions bot commented May 9, 2020

This issue has been open one year with no activity. Consequently, it is being marked with the "stale" label. What this means is that the issue will be automatically closed in 30 days unless more comments are added or the "stale" label is removed. Comments that provide new information on the issue are especially welcome: is it still reproducible? did it appear in other contexts? how critical is it? etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant