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

Abstract type and private type don't have same behavioir with type equality #7739

Closed
vicuna opened this issue Feb 21, 2018 · 11 comments
Closed
Assignees

Comments

@vicuna
Copy link

vicuna commented Feb 21, 2018

Original bug ID: 7739
Reporter: kantian
Assigned to: @garrigue
Status: confirmed (set by @garrigue on 2018-02-26T06:26:13Z)
Resolution: open
Priority: normal
Severity: minor
Category: typing
Related to: #7747
Monitored by: @gasche @lpw25

Bug description

After a discussion on OCaml discuss, I noticed there is a different behavior between abstract types and private aliased types against type equality. Leo White advised me to report this on Mantis, hence I do.

Steps to reproduce

type (_,_) eq = Refl : ('a, 'a) eq

module M : sig
  type t
  val eq : (t, int) eq
end = struct
  type t = int
  let eq = Refl
end

module N : sig
  type t = private int
  val eq : (t, int) eq
end = struct
  type t = int
  let eq = Refl
end

let (i : M.t) = match M.eq with Refl -> 1;;
val i : M.t = <abstr>

let (i : N.t) = match N.eq with Refl -> 1;;
Error: This expression has type int but an expression was expected of type N.t
@vicuna
Copy link
Author

vicuna commented Feb 26, 2018

Comment author: @garrigue

Interesting. In the case of an abstract type, we add an equation and are done.
The question is whether it is safe to do the same for private abbreviations, i.e. add an equation and forget the original private definition. Intuitively this seems correct, but I need to think a bit more about it.
(A safer way might be to call mcomp on the definitions before adding the abbreviation).

@vicuna
Copy link
Author

vicuna commented Feb 26, 2018

Comment author: @gasche

From the soundness point of view I think there is no problem: it is always sound to forget a type definition, so from type t = private int keep only the abstract type type t, and then to add t = int by using the equation. This way to add equations can always be used as a fall-back when we don't know how to push the equation into the definition.

@vicuna
Copy link
Author

vicuna commented Feb 26, 2018

Comment author: kantian

I tend to be agree with gasche, and we can encode his argumentation with functor.

module M : sig 
  type t = private int
  val eq : (t, int) eq
end = struct
  type t = int
  let eq = Refl
end

module Forget
(T : sig type t end)
(M : sig
  type t = private T.t
  val eq : (t, T.t) eq
end) : sig
  type t
  val eq1 : (t, T.t) eq
  val eq2 : (t, M.t) eq
end = struct
  type t = M.t
  let eq1 = M.eq
  let eq2 = Refl
end

let module N = Forget (struct type t = int end) (M) in
let i = match N.eq1 with Refl -> (1 : N.t) in
match N.eq2 with Refl -> (i : M.t);;
- : M.t = 1

@vicuna
Copy link
Author

vicuna commented Feb 26, 2018

Comment author: @garrigue

My problem is not soundness, but principality:
What do we do when both sides are private abbreviations?
Should we also refine other nominal types (with the same problem)?
It might be ok, but requires a proper argument.
Actually, subtyping seems problematic: when we equate two private abbreviations, choice of the one we keep is going to be relevant.

@vicuna
Copy link
Author

vicuna commented Feb 26, 2018

Comment author: kantian

I'm not sure I understand your questions, since principality problematic is not clear to me.

By the way, currently one can rely on elemantary theory of equality to perform the desired cast. This works in all situations (abstract type, private abbreviations for both types...)

module Eq = struct
  type (_,_) t = Refl : ('a,'a) t
  let sym : type a b. (a,b) t -> (b,a) t = fun Refl -> Refl
  let trans : type a b c. (a,b) t -> (b,c) t -> (a,c) t =
    fun Refl Refl -> Refl
  let cast : type a b. (a,b) t -> a -> b = fun Refl x -> x
end

type ('a,'b) eq = ('a,'b) Eq.t = Refl : ('a,'a) eq

module M : sig
  type t = private int
  val eq : (t, int) eq
end = struct
  type t = int
  let eq = Refl
end

module N : sig
  type t = private M.t
  val eq : (t, M.t) eq
end = struct
  type t = M.t
  let eq = Refl
end

let m1 = Eq.(cast @@ sym M.eq) 1;;
val m1 : M.t = 1

let n1 = Eq.(cast @@ sym N.eq) m1;;
val n1 : N.t = 1

let n1bis = Eq.(cast @@ sym @@ trans N.eq M.eq) 1;;
val n1bis : N.t = 1

I have just a question: what did you mean by this in your first reply?

(A safer way might be to call mcomp on the definitions before adding the abbreviation).

@vicuna
Copy link
Author

vicuna commented Feb 26, 2018

Comment author: @garrigue

Then here is an example of the problem

type (_,_) eq = Refl : ('a, 'a) eq

module M : sig type t = private int  val eq : (t, int) eq end =
  struct type t = int  let eq = Refl end

module N : sig type t = private M.t  val eq : (t, int) eq end =
  struct type t = M.t  let eq = M.eq end

module P : sig type t = private N.t  val eq : (t, M.t) eq end =
  struct type t = N.t  let eq = N.eq end

let f1 (i : P.t) = match P.eq with Refl -> (i :> int);;
let f2 (i : P.t) = match P.eq with Refl -> (i :> N.t);;

There are two option for the match here: either replace P.t = private N.t by P.t = M.t
or replace M.t = private int by M.t = P.t.
In the first case, f1 succeeds but f2 fails, and in the second case f2 succeeds but f1 fails.
The second case is actually even more problematic, as it results in a definitional loop:

 M.t = P.t = private N.t and N.t = private M.t ...

But this doesn't mean that the first option is the correct one: f2 already types currently
by doing nothing, so that the first option is even incomparable with doing nothing.

@vicuna
Copy link
Author

vicuna commented Feb 27, 2018

Comment author: kantian

Ok, I think that I better understand the problem.

Your second case is interresting, it can currently be encoded in OCaml an it leads the type checker in an infinite loop. ;-)

Here is the first option:

module Opt1 = struct
  module rec M' : sig type t = private int end = M
  and N' : sig type t = private M'.t end = N
  and P' : sig type t val eq : (t, M'.t) eq end = P
end

(* f1 succeeds *)
let open Opt1 in
fun x -> match P'.eq with Refl -> (x : P'.t :> int);;
- : Opt1.P'.t -> int = <fun>

(* f2 fails *)
let open Opt1 in
fun x -> match P'.eq with Refl -> (x : P'.t :> N'.t);;
Error: Type P'.t is not a subtype of N'.t

Here your second option:

module Opt2 = struct
  module rec M' : sig type t end = M
  and N' : sig type t = private M'.t end = N
  and P' : sig type t = private N'.t val eq : (t, M'.t) eq end = P
end

(* f2 succeeds *)
let open Opt2 in
fun x -> match P'.eq with Refl -> (x : P'.t :> N'.t);;
- : Opt2.P'.t -> Opt2.N'.t = <fun>

(* with f1 the type checker loops infinitely and never returns 
   I had to kill utop *)
let open Opt2 in
fun x -> match P'.eq with Refl -> (x : P'.t :> int);;
Fatal error: exception UTop_main.Term(-11)

There is even a more agressive solution : forget both private abbreviations, but in this case none of your functions type check.

Now, I'm not really sure there is a safe solution regarding subtyping and principality problematic. Currently, the most safe way could be to rely on general principles of type equality as I did in my previous reply.

@github-actions
Copy link

github-actions bot commented May 7, 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.

@github-actions github-actions bot added the Stale label May 7, 2020
@lpw25 lpw25 removed the Stale label May 7, 2020
@garrigue
Copy link
Contributor

garrigue commented May 9, 2020

Extra comment: I was too fast in dismissing soundness as a problem here.
When this bug was reported, the way exhaustiveness was handled meant that losing an equation could make a pattern untypable, and break soundness. This is now fixed, but of course the principality problem is still there.

I'm currently working on adding equations for nominal types (#9042), but I think I will have to stick to abstract types, or be very restrictive on the compatibility of the two definitions.

@garrigue
Copy link
Contributor

garrigue commented May 9, 2020

By the way, I don't think this is a bug. An incompleteness, yes, but the specification only says that we add equations to abstract types.

@github-actions
Copy link

github-actions bot commented Oct 8, 2021

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.

@github-actions github-actions bot added the Stale label Oct 8, 2021
@github-actions github-actions bot closed this as completed Nov 8, 2021
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

3 participants