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

Nominal typing #413

Closed
jclark opened this issue Feb 3, 2020 · 20 comments
Closed

Nominal typing #413

jclark opened this issue Feb 3, 2020 · 20 comments
Assignees
Labels
enhancement Enhancement to language design implementation/inprogress Implementation inprogress incompatible Resolving issue may not be backwards compatible lang Relates to the Ballerina language specification status/inprogress Fixes are in the process of being added Type/Proposal Lang spec proposals

Comments

@jclark
Copy link
Collaborator

jclark commented Feb 3, 2020

Ballerina is fundamentally a structurally-typed language, but there are cases where nominal typing would be useful, both for improved error checking and for interop with other systems that use nominal typing. Examples of the latter include SQL (with distinct types) and GraphQL (the __typename feature).

At the moment we can simulate nominal typing like this:

const PointTypeName = "{some module}Point";

type Point record {
   PointTypeName _typeName = PointTypeName;
   float x;
   float y;
};

You can then say:

Point p = { x: 1.0, y: 2.0 };

and the _typeName field gets defaulted.

The planned functional constructor syntax combines nicely with this:

var p = Point(x = 1.0, y = 2.0);

Private fields in objects also provide a way to create the effect of nominal typing. See #43 (comment)

This relates to a number of other things in the language:

  • the error type has a reason field, which is also conventionally a module-qualified string; XML elements have a element name, which is a namespace-qualified string; we are trying to find a nice syntax for this Provide convenient access to XML element name #225, which could also be used for this
  • we want better support for enumerations (see Improved enumeration support #276)
  • we want support for immutable types (see Immutable structures without storage identity #338)
  • the _typeName field is interesting in that it has a single possible value, and that value is immutable, which means the field is effectively immutable; there are a couple of other language features that also potentially have immutable fields (for table primary keys and event stream timestamps)
@jclark jclark added enhancement Enhancement to language design lang Relates to the Ballerina language specification labels Feb 3, 2020
@jclark jclark self-assigned this Feb 3, 2020
@jclark
Copy link
Collaborator Author

jclark commented Feb 3, 2020

A message to ballerina-dev specifically highlights the case of wrapping an existing simple type. One design issue is to what extent to prioritize that compared to records. My sense is that approaching the problem as a something that is layered on records is going to be a better fit for the language as a whole and for JSON/GraphQL.

@jclark
Copy link
Collaborator Author

jclark commented Feb 3, 2020

The fundamental idea is integrating nominal types into a structural language by having some sort of named marker attached to values. I believe this is usually called brands or branded types in the literature. Modula-3 has a BRANDED keyword for this.

@jclark
Copy link
Collaborator Author

jclark commented Feb 3, 2020

GraphQL __typename is here:

https://graphql.org/learn/queries/#meta-fields

@tj800x
Copy link

tj800x commented Feb 3, 2020

It would be nice to be able to have a keyword that returned the org, module, and scope at compile-time, so that the "brand" wouldn't need to be maintained. In the C world this would be akin to the __ FILE __ and __ LINE __ macros, a bit like __ ORG __, __ MODULE __, and __ SCOPE __. __ FULLSCOPE __ might combine these three.

That would give us this, which feels like an improvement since the record is self-contained.

type Point record {
   PointTypeName _typeName = __FULLSCOPE__;
   float x;
   float y;
};

@tj800x
Copy link

tj800x commented Feb 3, 2020

My original suggestion was for using tuples to create simple distinct types. Essentially this allows the creation of a new "bottom" to a type hierarchy that is above the basic type. My hope was for a the basic type to be fully encapsulated and not easily accessible. I had envisioned the need to use casting to convert to/from the basic type (except for literals). I still think this is the cleanest approach and worthy of some thought for simple distinct types (a two element tuple -- one value and one constant.)

type Name distinct string;
type City distinct string;

public function main() {

 Name name = "Thomas";
 Name nickname = "Tom";
 City city = "Washington"];
 
 name = nickname;
 
 // compile-time error : incompatible types: expected distinct City, found distinct Name'
 // name = city;
}

One option would be to allow the use of underscore on the RHS when destructuring a tuple, with the RHS underscore meaning "don't replace." For a tuple with constant elements this makes sense. Actually, for constants it wouldn't even be needed (see nickname assignment below).
Combining this with the __ FULLSCOPE __ macro would give us this:

type Name [string,_typeName=__FULLSCOPE__];
type City [string,_typeName=__FULLSCOPE__];

public function main() {

 Name name = ["Thomas", _ ];  // Slightly wonky syntax  
 Name nickname = ["Tom"];  // Better syntax
 City city = ["Washington"];

Having said all that, I'll be happy with whatever approach makes sense for the community as a whole.

@tj800x
Copy link

tj800x commented Feb 3, 2020

Here is a paper outlining how the authors added nominal typing capability to a structurally typed language: https://michael.homer.nz/Publications/ECOOP2015/paper.pdf .

@sanjiva
Copy link
Contributor

sanjiva commented Feb 8, 2020

If we had branded types we could use the brand of an error type as the default error reason value too.

@jclark jclark modified the milestones: 2021Rn, 2020R2 Mar 4, 2020
@jclark
Copy link
Collaborator Author

jclark commented Mar 14, 2020

Flow has an interesting concept of opaque types.

@sameerajayasoma
Copy link
Contributor

We've been using tags/brands to overcome similar issues in our Ballerina compiler codebase.
https://github.com/ballerina-platform/ballerina-lang/blob/v1.1.0/stdlib/bir/src/main/ballerina/src/bir/bir_model.bal#L365

@jclark
Copy link
Collaborator Author

jclark commented Mar 24, 2020

@sameerajayasoma That's an important use case. Nominal typing design ought to provide a solution that is usable for this.

@jclark
Copy link
Collaborator Author

jclark commented Apr 25, 2020

There's now a proposal.

jclark added a commit that referenced this issue Apr 25, 2020
@sanjiva
Copy link
Contributor

sanjiva commented Apr 27, 2020

James why do you say this:

An error error type error<R> means that the detail record must belong to the type
    readonly & record {| *R; (anydata|readonly)...; |}
In a match-statement, a error-match-pattern with a user-defined error type should match only if the shape of the value belongs to type.

Why can't I say error<readonly & record {| int x; |}> to create an error with a detail record that is not open? Why do we require error detail records to be open?

@jclark
Copy link
Collaborator Author

jclark commented Apr 27, 2020

It would get very confusing with &. The kind of openness you want with errors is more like objects, where belonging to an object type means you have at least the specified fields, but the constructor cannot specify fields other than those specified.

@sameerajayasoma
Copy link
Contributor

sameerajayasoma commented Apr 28, 2020

Regarding the intersection of object types.

  • If object A has field f of object type X and object B has a field f of object type Y, what would be the type of the field f in A & B given that X & Y is valid?

@jclark
Copy link
Collaborator Author

jclark commented May 1, 2020

Conclusions from meeting:

  1. only apply intersection to abstract object types
  2. only provide access to type id via typedesc and typeof
  3. type id is record containing local id and module id consisting of organization name, module name, version
  4. need to create issue on langlib versioning and how that really works, in particular as regards including nominal types defined in langlib

@jclark jclark added the status/pending Design is agreed and waiting to be added label May 1, 2020
@jclark
Copy link
Collaborator Author

jclark commented May 1, 2020

@sameerajayasoma I added an issue on function intersection types #506.

jclark added a commit that referenced this issue May 1, 2020
@jclark jclark added status/inprogress Fixes are in the process of being added and removed status/pending Design is agreed and waiting to be added labels May 1, 2020
@jclark
Copy link
Collaborator Author

jclark commented May 1, 2020

The error-specific parts of this will be covered in #509. Parts related to intersection in #508. This issue just covers the distinct type feature.

@jclark
Copy link
Collaborator Author

jclark commented May 1, 2020

Additional changes still needed to the spec:

  • Add introductory subsection about type-ids to 5.1
  • In "Distinct types" section, explain what it means for a value to belong to a distinct type
  • Explain how an error or object value gets type-ids when it is constructed
  • Add function to lang.typedesc to get type-ids; also needs record type for type-id
  • New explanation of built-in abstract object types that allows for distinct types
  • Use distinct object type for Listener
  • Use distinct object type for Iterable
  • Change __init to init for objects and modules
  • toString for object should show type-ids consistently with error
  • Make object and error predeclared as module prefixes
  • Object type can include non-abstract object type

@jclark jclark added the incompatible Resolving issue may not be backwards compatible label May 1, 2020
jclark added a commit that referenced this issue May 3, 2020
jclark added a commit that referenced this issue May 3, 2020
@jclark
Copy link
Collaborator Author

jclark commented May 3, 2020

Semantics explained in commit 2baf5ae

jclark added a commit that referenced this issue May 3, 2020
jclark added a commit that referenced this issue May 4, 2020
jclark added a commit that referenced this issue May 4, 2020
jclark added a commit that referenced this issue May 4, 2020
jclark added a commit that referenced this issue May 4, 2020
jclark added a commit that referenced this issue May 4, 2020
Introduce object:Iterator
Part of #413.
@jclark
Copy link
Collaborator Author

jclark commented May 5, 2020

Decided today not to require value:toString's use of toString on objects to require distinct type.

jclark added a commit that referenced this issue May 14, 2020
Rework type inclusion section.
Part of #413.
jclark added a commit that referenced this issue May 14, 2020
@jclark jclark closed this as completed May 14, 2020
@hasithaa hasithaa added the implementation/inprogress Implementation inprogress label Jun 17, 2020
@praneesha praneesha added the Type/Proposal Lang spec proposals label Aug 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Enhancement to language design implementation/inprogress Implementation inprogress incompatible Resolving issue may not be backwards compatible lang Relates to the Ballerina language specification status/inprogress Fixes are in the process of being added Type/Proposal Lang spec proposals
Projects
None yet
Development

No branches or pull requests

6 participants