Add a new type kind Type_external#13712
Conversation
|
remaining issues:
|
|
For the record / historical note: I believe that this comes from RFC #4, and in particular #9042 (comment) from @lpw25. |
|
I'm happy to review this / discuss the design here. My unfinished attempt at this is here. Glad to see this being added! |
| ^^^ | ||
| Error: This pattern matches values of type "'a t" | ||
| but a pattern was expected which matches values of type "int" | ||
| Error: This pattern should not be a constructor, the expected type is "int" |
There was a problem hiding this comment.
This error message provides less information than before.
| ^^^^ | ||
| Error: The constructor "Cons" has type "v" but an expression was expected of type | ||
| "int" | ||
| Error: This expression should not be a constructor, the expected type is "int" |
There was a problem hiding this comment.
As above, there is less information than before.
There was a problem hiding this comment.
This can be fixed independently: the kind mismatch error messages was just not updated to use the improved handling of nominal expression that was added for type expression clashes.
There was a problem hiding this comment.
I think of doing that as a follow up PR.
| ^^^^^^^^^^^^^^^^ | ||
| Error: This pattern matches values of type "int ref" | ||
| but a pattern was expected which matches values of type "int" | ||
| Error: This pattern should not be a record, the expected type is "int" |
There was a problem hiding this comment.
The fact that the pattern is for references is lost in the new message.
|
I am planning to separately PR the commit 478a177. |
goldfirere
left a comment
There was a problem hiding this comment.
I did not review:
- Changes covered in #13820 (which have been reviewed separately)
- Changes in ocamldoc (which I am not familiar with)
Overall looks good! Though I'm a little sad this does not go as far as my attempt at this, which would allow (for example) int-like optimizations for external "int"s.
This will need a chapter in the manual.
What's the status of design review? Has there been a broad enough consensus to add this feature to the language? I'm definitely in support.
|
Just to make sure I understand: in this PR, is it allowed to have a nominal abstract type in the interface with a concrete type definition in the implementation? eg ? |
Incidentally, this PR looks like it could be useful for runtime types (see Section 3 of https://www.math.nagoya-u.ac.jp/~garrigue/papers/ocaml2013.pdf). |
A short answer is no: |
|
But modifying |
|
Coercion from datatype to nominal type was in the nominal RFC, but this is not in this version, as it raises questions as to whether abstract type in libraries should be converted to nominal types, and with which name. |
|
Note that in that case the name should already be given in the datatype definition, otherwise it becomes possible to export the same datatype with two different names, which is unsound. |
|
Thanks for the response @t6s and @garrigue. Indeed I raised the question because this coercion was mentioned in #9042 (comment). |
|
We discussed the feature at the last type-system meeting. We generally agree that this is a good feature to have, in particular to be able to disable the existing behavior on current-module abstract types which has more downsides than upsides. The plan is to merge the PR as soon as it is ready, and that some documentation is written in the reference manual -- @Octachron and myself have been looking at how to document this. NamingThe feature is called "nominal types" here, coming from the RFC, but I think that "external types" makes more sense. The story is that these types are intended to be populated from the FFI (and possibly from unsafe casts/primitives), and their syntax precisely uses Do you agree with this naming change suggestion? Should we use DocumentationHere is our current plan for documentation:
|
I thought that we agreed to remove the previous behavior at once. So it is not "going to change", but "has changed". |
|
@gasche I think the renaming reasonable and have done that. |
Implementation PR: ocaml#13712 Previous RFC (outdated): ocaml/RFCs#4
Implementation PR: ocaml#13712 Previous RFC (outdated): ocaml/RFCs#4
1. distinguish string and bytes, and 2. judge locally defined abstract types compatible (by disabling non_aliasable).
- Changes - add and promote tests - fix variance
Implementation PR: ocaml#13712 Previous RFC (outdated): ocaml/RFCs#4
|
I am realizing now that the change in this PR has farther-reaching implications than I assumed. This is now rejected: type t = A
type u = A
let test (x : (t, u) Type.eq) =
match x with _ -> .or this type t = { x : int }
type u = { x : int }
let test (x : (t, u) Type.eq) =
match x with _ -> .I thought that this would only start being rejected for abstract types, type t
type ubut in fact datatypes are also affected. The same reasoning applies that these examples are not robust to being put under a module abstraction, and in fact we did discuss the impact on empty types |
Implementation PR: ocaml#13712 Previous RFC (outdated): ocaml/RFCs#4
|
Well, yes, since now the compatibility check is purely structural, any two types with the same structure cannot be distinguished. This has always been the case when one of the types was coming from another module. |
|
I can see opam-repo tests were run in 2019 in #8900 (comment) to test the implications of this backward incompatible change. Have something more recent been run? 🤔 |
Implementation PR: ocaml#13712 Previous RFC (outdated): ocaml/RFCs#4
Implementation PR: ocaml#13712 Previous RFC (outdated): ocaml/RFCs#4
Implementation PR: ocaml#13712 Previous RFC (outdated): ocaml/RFCs#4
Implementation PR: ocaml#13712 Previous RFC (outdated): ocaml/RFCs#4
I believe this is because the backward compatibility issue only shows up as a warning since |
|
I don't want to bikeshed syntax, but I am worried that this feature isn't the right direction for exposing the 'generative' / 'nominal' behaviour of OCaml's datatypes. The current syntax: type t = external "name"is misleading: it looks like a type alias for type t = Foo.bar = external "t" = Foo | BarIn his comment, @lpw25 suggested a more coherent approach: introduce an abstract nominal datatype, exploiting that datatypes are generative already. The idea is to refactor type type_kind =
| Abstract
| Datatype of { kind : datatype_kind option }
| Alias of { kind : alias_kind }
type datatype_kind =
| Variant of ...
type alias_kind =
| Expansion of { privacy : private_flag; expansion : type_expr }
| Manifest of { manifest : Path.t; kind : datatype_kind }This captures the semantics that
Subtyping already permits Unfortunately, I'm not too sure what the syntax for such a construction should be. If we had the benefit of hindsight, perhaps type t (** maps to [Abstract] *)
type t = int (** maps to [Alias { kind = Expansion { ... } }] *)
type t = Foo.bar = Foo | Bar (** maps to [Alias { kind = Manifest { path = Foo.bar; ... } }] *)
new type t (** maps to [Datatype { kind = None }] *)
new type t = Foo | Bar (** maps to [Datatype { kind = Variant ... }] *)In the case of external types, we would simply have: new type float
new type int
... |
|
I infact just noticed that @lpw25's comment doesn't correctly capture the erasure of a manifest type via subtyping (which would permit module Foo = struct
type t = Foo | Bar
end
module Bar : sig
type t = Foo | Bar
end = struct
type t = Foo.t = Foo | Bar
endPerhaps the fix is to add a type type_kind =
...
| New_abstract
| New_datatype of { kind : datatype_kind }with syntax: new type t (** maps to [New_abstract] *)
new type t = Foo | Bar (** maps to [New_datatype] *) |
Implementation PR: ocaml#13712 Previous RFC (outdated): ocaml/RFCs#4
This PR adds a new kind
Type_external <string>for abstract types with a name.(NB:
Type_nominalwhen this PR was created; renamed after the discussion below)Users can declare an abstract type with a name with the following syntax:
type t = external "t"This new kind will enable two things:
Type.eq.intcan be distinguished from other abstract types.Should subsume #9042 and be a step toward #8900 .
Coauthored by @garrigue .