microsoft/TypeScript

Support some non-structural (nominal) type matching

iislucas opened this issue ยท 495 comments

Proposal: support non-structural typing (e.g. new user-defined base-types, or some form of basic nominal typing). This allows programmer to have more refined types supporting frequently used idioms such as:

  1. Indexes that come from different tables. Because all indexes are strings (or numbers), it's easy to use the an index variable (intended for one table) with another index variable intended for a different table. Because indexes are the same type, no error is given. If we have abstract index classes this would be fixed.

  2. Certain classes of functions (e.g. callbacks) can be important to be distinguished even though they have the same type. e.g. "() => void" often captures a side-effect producing function. Sometimes you want to control which ones are put into an event handler. Currently there's no way to type-check them.

  3. Consider having 2 different interfaces that have different optional parameters but the same required one. In typescript you will not get a compiler error when you provide one but need the other. Sometimes this is ok, but very often this is very not ok and you would love to have a compiler error rather than be confused at run-time.

Proposal (with all type-Error-lines removed!):

// Define FooTable and FooIndex
nominal FooIndex = string;  // Proposed new kind of nominal declaration.
interface FooTable {
  [i: FooIndex]: { foo: number };
}
let s1: FooIndex;
let t1: FooTable;

// Define BarTable and BarIndex
nominal BarIndex = string; // Proposed new kind of nominal declaration.
interface BarTable {
  [i: BarIndex]: { bar: string };
}
let s2: BarIndex;
let t2: BarTable;

// For assignment from base-types and basic structures: no type-overloading is needed.
s1 = 'foo1';
t1 = {};
t1[s1] = { foo: 1 };

s2 = 'bar1';
t2 = { 'bar1': { bar: 'barbar' }};

console.log(s2 = s1); // Proposed to be type error.
console.log(s2 == s1); // Proposed to be type error.
console.log(s2 === s1); // Proposed to be type error.

t1[s2].foo = 100; // Gives a runtime error. Proposed to be type error.
t1[s1].foo = 100;

function BadFooTest(t: FooTable) {
  if (s2 in t) {  // Proposed to be type error.
    console.log('cool');
    console.log(t[s2].foo); // Proposed to be type error.
  }
}

function GoodBarTest(t: BarTable) {
  if (s2 in t) {
    console.log('cool');
    console.log(t[s2].bar);
  }
}

BadFooTest(t1); // Gives runtime error;
BadFooTest(t2); // No runtime error, Proposed to be type error.
GoodBarTest(t1); // Gives runtime error; Proposed to be type error.
GoodBarTest(t2);

Is there a better keyword here than "abstract" ? People are going to confuse it with "abstract class".

+Needs Proposal

w.r.t. Needs Proposal: do you mean how to implement it? For compilation to JS, nothing needs to be changed. But would need internal identifiers for new types being introduced and an extra check at assignment.

Regarding a name, what about "nominal" types? Seems pretty common in literature.

We're still writing up the exact guidelines on suggestions, but basically "Needs Proposal" means that we're looking for someone to write up a detailed formal explanation of what the suggestion means so that it can be more accurately evaluated.

In this case, that would mean a description of how these types would fit in to all the various type algorithms in the spec, defining in precise language any "special case" things, listing motivating examples, and writing out error and non-error cases for each new or modified rule.

@RyanCavanaugh Thanks! Not sure I have time for that this evening :) but if the idea would be seriously considered I can either do it, to get someone on my team to do so. Would you want an implementation also? Or would a clear design proposal suffice?

@iislucas no implementation is necessary for "Needs Proposal" issues, just something on the more formal side like Ryan described. No rush ;)

There is a workaround that I use a lot in my code to get nominal typing, consider:

interface NominalA {
   'I am a nominal type A, nobody can match me to anything I am not': NominalA;
    value: number;
}

interface NominalB {
   'I am a nominal type B, mostly like A but yet quite different': NominalB;
   value: number;
}

// using <any> on constructing instances of such nominal types is the price you have to pay
// I use special constructor functions that do casting internally producing a nominal object to avoid doing it everywhere
var a : NominalA = <any>  { value: 1 };
var b : NominalB = <any>  { value: 2 };

a = b; // <-- problema

Neat trick! Slight optimization, you can use:

var a = <NominalA>  { value: 1 };
var b = <NominalB>  { value: 2 };

(Slightly nicer/safer looking syntax)
[Shame it doesn't work for creating distinct types for string that you want to be indexable]

@Aleksey-Bykov nice trick. We have nominal Id types on the server (c#) that are serialized as strings (and we like this serialization). We've wondered of a good way to do that without it all being string on the client. We haven't seen bugs around this on the client but we still would have liked that safety. Based on your code the following looks promising (all interfaces will be codegened):

// FOO 
interface FooId{
    'FooId':string; // To prevent type errors
}
interface String{   // To ease client side assignment from string
    'FooId':string;
}
// BAR
interface BarId{
    'BarId':string; // To prevent type errors
}
interface String{   // To ease client side assignment from string
    'BarId':string;
}


var fooId: FooId;
var barId: BarId;

// Safety!
fooId = barId; // error 
barId = fooId; // error 
fooId = <FooId>barId; // error 
barId = <BarId>fooId; // error

// client side assignment. Think of it as "new Id"
fooId = <FooId>'foo';
barId = <BarId>'bar';

// If you need the base string 
// (for generic code that might operate on base identity)
var str:string;
str = <string>fooId;
str = <string>barId;  

We could look at an implementation that largely left the syntax untouched: perhaps we could add a single new keyword that switches on "nominality" for a given interface. That would leave the TypeScript syntax largely unchanged and familiar.

class Customer {
    lovesUs: boolean;
}

named class Client {
    lovesUs: boolean;
}

function exampleA(customer: Customer) {

}

function exampleB(customer: Client) {

}

var customer = new Customer();
var client = new Client();

exampleA(customer);
exampleA(client);

exampleB(customer); // <- Not allowed
exampleB(client);

So you can use a Client where a Customer is needed, but not vice versa.

You could fix the error in this example by having Customer extend Client, or by using the correct named type - at which point the error goes away.

You could use the "named" switch on classes and interfaces.

You could use the "named" switch on classes and interfaces.

๐Ÿ‘

You could also use it to make a type nominal in a specific context, even if the type was not marked as nominal:

function getById(id: named CustomerId) {
    //...

You could also use it to make a type nominal in a specific context

What would that mean?

When used as part of a type annotation, it would tell the compiler to compare types nominally, rather than structurally - so you could decide when it is important for the exact type, and when it isn't.

It would be equivalent to specifying it on the class or interface, but would allow you to create a "structural" interface that in your specific case is treated as "nominal".

Or, I have jumped the shark :) !

An example of an error (or non-error) would be nice. I can't figure out how you'd even use this thing

interface CustomerId { name: string }
interface OrderId { name: string }
function getById(id: named CustomerId) {
    //...
}
var x = {name: 'bob'};
getById(x); // Error, x is not the nominal 'named CustomerId' ?

function doubleNamed1(a: named CustomerId, b: named OrderId) {
    a = b; // Legal? Not legal?
}
function doubleNamed2(a: named CustomerId, b: named CustomerId) {
    a = b; // Legal? Not legal?
}
function namedAnon(x: named { name: string }) {
     // What does this even mean? How would I make a value compatible with 'x' ?
}

This is why I'm not a language designer :)

I've shown in the example below that the keyword applies for the scope of the variable. If you make a parameter nominal, it is nominal for the whole function.

interface CustomerId { name: string }
interface OrderId { name: string }
function getById(id: named CustomerId) {
    //...
}
var x = {name: 'bob'};
getById(x); // Error, x is not the nominal 'named CustomerId'

function doubleNamed1(a: named CustomerId, b: named OrderId) {
    a = b; // Not legal, a is considered to be a nominal type
}
function doubleNamed2(a: named CustomerId, b: named CustomerId) {
    a = b; // Legal, a is compared nominally to b and they are the same type
}
function singleNamed1(a: named CustomerId, b: CustomerId) {
    a = b; // Legal, a is compared nominally to b and they are the same type
}
function namedAnon(x: named { name: string }) {
     // Compiler error - the "named" keyword can only be applied to interfaces and classes
}

I admit that the notion of marking an item as nominal temporarily as per these recent examples may have been a little flippant - in the process of thinking through the implications of the feature I'm happy to accept it may be a terrible idea.

I'd hate for that to affect the much more straightforward idea marking a class or interface as nominal at the point it is defined.

Will need an inline creation syntax. Suggestion, a named assertion:

var x = <named CustomerId>{name: 'bob'};  // x is now named `CustomerId`
getById(x); // okay

Perhaps there can be a better one.

I wonder what the use cases for library developers are to not request nominal type checking via name.

Wouldn't you always be on the safe side if you use name by default? If the caller does have the right type, all is fine. If he doesn't, he must convert it (e.g. using the syntax @basarat suggested). If the conversion works, but doesn't work as expected, it's the user's fault and not the library developer's fault.

Maybe the whole problem is the duck typing system itself. But that's one problem TypeScript shouldn't solve, I suppose.

Not to sound like a sour puss, but being a structural type system is a fork in the road early on in how the type system works. We intentionally went structural to fit in better with JavaScript and then added layer upon layer of type system machinery on top of it that assumes things are structural. To pull up the floor boards and rethink that is a ton of work, and I'm not clear on how it adds enough value to pay for itself.

It's worth noting, too, the complexity it adds in terms of usability. Now people would always need to think about "is this type going to be used nominally or structurally?" Like Ryan shows, once you mix in patterns that are common in TypeScript the story gets murky.

It may have been mentioned already, but a good article for rules of thumb on new features is this one: http://blogs.msdn.com/b/ericgu/archive/2004/01/12/57985.aspx

The gist is that assume every new feature starts at -100 points and has to pay for itself in terms of added benefit. Something that causes a deep rethink of the type system is probably an order of magnitude worse. Not to say it's impossible. Rather, it's highly unlikely a feature could be worth so much.

Agree with Jonathan here. I would have to see an extremely thorough proposal with some large code examples that prove this doesn't quickly become unmanageable. I have a hard time imagining how you could effectively use this modifier in a restricted set of circumstances without it leaking everywhere and ending up with you needing to use it on every type in your program (or giving up entirely on things like object literals). At that point you're talking about a different language that is basically incompatible with JavaScript.

Remember that nominal systems come with pain too and have patterns they don't represent as well. The trade off to enable those patterns with a structural system is the occasional overlap of structurally equal but conceptually different types.

So the most common use case for this (that I can think of) is type-safe ids. Currently, you can create these in TypeScript by adding a private member to a class (or a crazy identifier on an interface, although that only reduces the chance, whereas the private member trick works as expected).

You have already made the decision that you want a nominal type when you create a type safe id class, because that is the purpose of such a class (and is the reason you aren't simply using number).

So my question is as follows, this code does what a lot of people want:

    class ExampleId {
        constructor(public value: number){}
        private notused: string;
    }

i.e. you cannot create another type that will satisfy this structure, because of the private member...

  1. Would it be possible to formalise this behaviour with a keyword so the private member isn't needed?
  2. Would it be possible to get this behaviour for interfaces?

The first of these two questions would probably cover 80% of the use cases. The second would allow similar cases and would be very useful from a .d.ts perspective.

This limits the feature to the creation of types that cannot be matched, which is already possible as described and for classes simply moves a "magic fix" into a more deliberate keyword.

I would be happy to write up something for this.

Certainly feel free to try to write up something more complete that can be evaluated, although I will be honest and say the chances of us taking a change like seem quite slim to me.

Another data point to consider is that TypeScript classes had this behavior by default for some time (ie always behaved as a nominal type) and it was just very incongruous with the rest of the type system and ways in which object types were used. Obviously the ability to turn nominal on/off is quite different from always on but something to consider nonetheless. Also, as you note this pattern does allow some amount of nominal typing today, so it would be interesting to see if there are any codebases that have used this intermixing to a non-trivial degree (in a way that isn't just all nominal all the time).

Note: lets not mix up the baby and bathwater here: the proposal in this
issue is not a nominal keyword for any type, but to support a specific
interface declaration of a nominal type. Nominal types are easy get right,
and pretty well understood to provide value; while a 'sticky' nominal type
annotation is tricky to do right. I'd suggest moving discussion of a
anywhere nominal type-tag to a different issue so as not to confuse the
two.

On Fri, Aug 1, 2014 at 4:37 PM, Dan Quirk notifications@github.com wrote:

Certainly feel free to try to write up something more complete that can be
evaluated, although I will be honest and say the chances of us taking a
change like seem quite slim to me.

Another data point to consider is that TypeScript classes had this
behavior by default for some time (ie always behaved as a nominal type) and
it was just very incongruous with the rest of the type system and ways in
which object types were used. Obviously the ability to turn nominal on/off
is quite different from always on but something to consider nonetheless.
Also, as you note this pattern does allow some amount of nominal typing
today, so it would be interesting to see if there are any codebases that
have used this intermixing to a non-trivial degree (in a way that isn't
just all nominal all the time).

โ€”
Reply to this email directly or view it on GitHub
#202 (comment)
.

Lucas Dixon | Google Ideas

@iislucas - as mentioned earlier, structural and nominal are fundamental choices in the type system. Any time you rethink part of the fundamental design choices, you need to understand the full impact. Even if it seems to be isolated to a small set of scenarios.

The best way to full understand the impact is to have a more complete suggestion. I wouldn't confuse @danquirk's response as throwing the baby with the bathwater, but instead as the minimal amount of work any proposal would need that touches a fundamental design choice.

I agree that a fully proposal is a good idea, and I'll do that. I worked a
long time in type-systems, so I'm pretty confident in my understanding of
whats involved here. But there are wildly different things being suggested.
So probably good to put each one into it's own discussion :)

On Fri, Aug 1, 2014 at 5:17 PM, Jonathan Turner notifications@github.com
wrote:

@iislucas https://github.com/iislucas - as mentioned earlier,
structural and nominal are fundamental choices in the type system. Any time
you rethink part of the fundamental design choices, you need to understand
the full impact. Even if it seems to be isolated to a small set of
scenarios.

The best way to full understand the impact is to have a more complete
suggestion. I wouldn't confuse @danquirk https://github.com/danquirk's
response as throwing the baby with the bathwater, but instead as the
minimal amount of work any proposal would need that touches a fundamental
design choice.

โ€”
Reply to this email directly or view it on GitHub
#202 (comment)
.

Lucas Dixon | Google Ideas

@jonathandturner just wanted to check what you think of the example use-cases proposed in the description. Do they make sense? Happy to clarify/expand on them if that helps (and also of #202 (comment) which is a case of entry (2) in the issue description)

And also w.r.t. the suggestion of @Aleksey-Bykov : #202 (comment)
it seems that it could be implemented as a simple abbreviation to existing type-script without introducing any new concepts. The only optimization that would make sense to add would be for the compiler to optimize away self-referential nominal field.

I realize that I've offered to do something but not actually 100% sure what you'd like me to write - I could look through code and start pointing to how I would implement it? but maybe there's something more to do before that?

Nominal types (and new kinds of indexable types) that don't get confused between each other would be a big improvement in my opinion. They let you separate layers of abstraction and implementation details. And they are widely used in other typed functional languages too (F#, Haskell, SML/Ocaml, etc).

Thanks,

Hi @iislucas,

For a proposal for nominal types, you'll need to show strong use cases. As listed in the design goals for TypeScript: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals, being structural is something that TypeScript is committed to. The assumption that checking is structural is carried throughout the type checker, the spec, and how it's used in practice. In short, augmenting or going away from structural is going to be uphill all the way.

Adding some additional nominal checking would need to be weighed against the benefit. For this to have a chance of success, the types of new patterns that the new nominal capabilities provide would need to enable fundamental improvements in how JavaScript is typed.

Looking at the examples in the original post, I'm not sure which JavaScript patterns this enables. Starting with that might the best place. Also, you may want to look at other new functionality, like type guards, and how this might impact that.

@jonathandturner I don't see why you make it seem like an exclusive choice of nominal vs structural: sometimes structural checking is what you want, sometimes nominal is. There are already a set of base nominals (string, number, etc). What I see as a good insight in typescript, and in the design goals, is that structural checking is important and will be supported. Many typed languages don't have this. It sounded to me that support for structural checking in the design goals was saying it's something TypeScript wanted to include rather than a claim about never adding an more base-types or never supporting nominal definitions. I don't see this as being a philosophical question that undermines TypeScript.

w.r.t. JavaScript patterns: it is not a JavaScript pattern that it enables. It is enables stronger levels of abstraction and more type-checking.

w.r.t. difficulty: support for nominal types is the easiest kind of extension there is to a type-system. We're not here proposing dependent types, or any fancy new computation in typing (although coming from that universe, I'm tempted... ;-) ). Just the ability to define new nominal types/interfaces.

But if I've mis-understood TypeScirpt's goals, and it is a goal to not support nominal types, then of course much stronger arguments and examples are needed.

@iislucas - TypeScript's main goal is to enable large-scale JavaScript applications. It does this by staying very close to JavaScript. Most of what the type system was built for is working with JavaScript and enabling JavaScript patterns in a type-safe way.

When we were setting ourselves up for how to go about that, we went back and forth a bit on supporting both nominal and structural. At one point, we had a structural system that would tag classes with "brands" that would allow users to use them more nominally.

The more we worked with the system, though, the more it felt like going completely structural seemed closer to the spirit of JavaScript and how flexible it is in practice. Once we realized that, we went completely in the direction of structural and aren't looking to backtrack.

That's why I said it would take a lot of strong benefits to make us backtrack and re-think going fully structural.

@jonathandturner - thanks for the history and background. JS has generally been a difficult platform for creating large scale JS applications, and supporting this is why I and my team use it. Type-abstraction is specifically thought to help with this, and in my experience in other languages, it helps a lot. There's a long history of it in many languages, but primarily in the typed functional programming world (e.g. SML and Haskell). So if the problem is that it may be difficult to (re)implement, the thing to do is look into the details of what it would take to support it. If it is easy, then it's less of a issue. If it's hard and requires significant refactoring, then it makes sense to discuss examples and try and persuade more people - probably to do some research and point to it's role in other languages/frameworks.

The proposal of @Aleksey-Bykov : #202 (comment) seems easy to me. What would you say the next step in exploring this is for those of us who think it would make the language better? Is the next step a patch for typescript, or more examples using the proposed TypeScript syntax, along with errors/compilations?

The implementation is not particularly difficult (nominal classes existed before, and you can hack the same effect in today via private members). What's difficult is reconciling how nominal code would interact with structural parts of a program and all the JS libraries that are built on structural typing. As mentioned, we essentially had a system like this before (classes were nominal, interfaces and object literals were structural) and it did not mesh very well with what people already do with JS.

Good to know the implementation is not difficult.

w.r.t. existing libraries: how about leaving all the existing JS libraries as is (using structural typing)? Nominal types can be included incrementally in new projects to catch more errors at compile time and support abstraction in newer larger software projects - it gives a nice way to move towards increased reliability in a project, e.g. to start you might introduce some nominal types for different kinds of strings to avoid mixing them up.

I would start using new nominal types in my projects pretty quickly, but I don't mind that libraries I use don't. I can always introduce an abstraction layer (e.g. a new interface) by writing a new .d.ts file for an existing library when I feel it would help me (e.g. to start with, I might do so for things like socket identifiers for chrome-app environments, see: https://developer.chrome.com/apps/sockets_tcp ).

@danquirk, your major argument is that all existing libs are structural, people tend to think structural, etc

Well fine, having nominal types in addition to everything else being structural by default isn't going to hurt, is it?

The difficulties of matching structural types to nominal and shortcomings of not being able to do so 100% nicely is going to be a problem of nominal types where compromises are possible since these are the problems of minority of nominal types and doesn't affect how structural types work which are in the heart of the language.

Well fine, having nominal types in addition to everything else being structural by default isn't going to hurt, is it?

An increase in complexity needs to more than pay for itself in terms of value added. Every language feature we add is another row and column in the matrix of interactions between different aspects of the language. Especially for something as core as the rules of type compat, we're talking about permanently complicating the language in a major way and potentially preventing future features.

The thing I'm not seeing in this thread is a bunch of real-world examples where people would have gotten desired behavior with nominal types, but instead got poor behavior because of structural types. When we have more urgent gaps in the language, those examples are very common and easy to find.

I don't see where the potential bad interactions would be - maybe you can say a bit more about that? Happy to think about that more and document what the expected interactions might be.

If you remove nominal types, strictly more types match, so removing it isn't difficult in the future. So, I don't see it as long-term challenge to maintain. Also, sound like it's not complicated to implement either.

w.r.t. more examples of where abstract types naturally occur, here's 3 groups that come off the top of my head:

  • Socket ids in pretty much all platforms (node, Chrome Apps, Firefox Add-ons; UDP and TCP).
  • User-names as keys in a table where you don't want to mix up usernames with other strings.
  • Crypto-keys or passwords; again to avoid mixup with other string values.

@RyanCavanaugh, Tiny types is where nominals would be so very handy. Consider:

interface Age {}
interface Height {}
interface Person {
   height: Height;
   age: Age;
}

function ageFrom(value: number): Age {
   if (value > 0) return <any> value;
   throw new Error('Invalid age');
}

function heightFrom(value: number): Height {
  if (value > 0) return <any> value;
  throw new Error('Invalid height');
}

function formatAge(age: Age): string {
   return (<any> age).toFixed(0);
}

function formatHeightheight: Height) : string {
   return (<any> height).toFixed(0);
}

var person: Person = { age: ageFrom(35), height: heightFrom(180) };
console.log(formatAge(person.height)); // <= problem!

This gives another type safety measure that prevents you from mistaking a height for an age. Which otherwise would be interchangeable if plain numbers were used.

In Haskell same thing is done by newtype and it's a crazy popular feature.

Your point that there is little demand for nominal types is flawed, there is no demand because no one ever seen them in JavaScript and don't know they could have existed let alone possible at all. People including myself coming from other languages where there is a feature like this would certainly appreciate it.

For that scenario my preferred solution would be something like F#'s Units of Measure ( #364). There's essentially a huge class of types inside number that cannot be specified as incompatible with one another. What you want are basically additional (user specified) primitive types.

There are scenarios where nominal types give you safety over structural types. There are also scenarios where nominal types create unnecessary friction trying to represent patterns that structural typing easily models. There are trade offs with each. Most languages I can think of have had to choose one or the other as trying to mix them is confusing or painful or both. Your latest example seems emblematic of some of my concerns. I take it you just want an error on the last line. But then in the same small snippet of code you've taken advantage of structural typing (a number is an Age since Age has no members) while also doing a nominal comparison a few lines later (to get an error using Height where Age is expected). Essentially every type comparison operation would require you to do the additional work of deducing whether the comparison was a structural or a nominal one, rather than having an easily predictable set of behavior.

@danquirk, I don't mind trading nominals for units of measure if... units of measure can be applicable to any type, not necessarily just numbers. Strings are the next after numbers problematic type that just too broad to be used safely. So doing something like this for string would be just great: "aleksey.bykov@gmail.com"<email>. Now if we go further down this road why not to make Dates capable of units? I remember lots of bugs when I did heavy calculations on dates cause by mistaking one date for another. Do you think units of measure have a better chance for making it as a feature?

Yeah I can see the applicability to other primitive types given JavaScript's penchant for string based APIs. We haven't had any serious discussions about UoM (ES6 features are keeping us fairly busy as far as new language stuff goes) but yes I would say it's more likely we do that at some point than add nominal semantics.

Put together an article on faking nominal types in TypeScript: https://gist.github.com/aleksey-bykov/0ab85f0b5e83fc848f85

Thank you! This is very helpful. Obviously, I've love to see native support this in TS, but this is the nicest workaround I've seen so far.

FWIW, I'm using a void field as a workaround. It works nicely and doesn't get in the way.

class Foo {};
let x : Foo = 42;  // OK :(

class Bar {
    _nominal_Foo: void;
};
let y : Bar = 42;  // Error :)

Playground.

I'm using a void field as a workaround

making it any is just as good. And I prefer the term Brand as that is what is used by the TypeScript team e.g. : https://github.com/Microsoft/TypeScript/blob/7b48a182c05ea4dea81bab73ecbbe9e013a79e99/src/compiler/types.ts#L693-L698

    export interface Expression extends Node {
        _expressionBrand: any;
        contextualType?: Type;  // Used to temporarily assign a contextual type during overload resolution
    }

The real type or a brand propeety is an empty enum, since enums are unique
in ts.
On Sep 21, 2015 8:19 PM, "Basarat Ali Syed" notifications@github.com
wrote:

I'm using a void field as a workaround

making it any is just as good. And I prefer the term brand as that is
what is used by the TypeScript team e.g. :
https://github.com/Microsoft/TypeScript/blob/7b48a182c05ea4dea81bab73ecbbe9e013a79e99/src/compiler/types.ts#L693-L698

export interface Expression extends Node {
    _expressionBrand: any;
    contextualType?: Type;  // Used to temporarily assign a contextual type during overload resolution
}

โ€”
Reply to this email directly or view it on GitHub
#202 (comment)
.

I agree that structural typing almost always creates abstractions that are far too wide to be practical in real-world scenarios. A concept is usually bound to a context and there's a lot of cases where two objects will accidentally share the same structure but shouldn't be interchangeable.

In my opinion, the default behavior should have been that structural type checking is used only where contracts are defined in terms of interfaces and nominal typing is used when contracts are defined in terms of concrete classes: concrete classes also makes the promise of an implementation which is a form of contract.

Consider the code below:

class Username {
    public value: string;

    constructor(value: string) {
        if (!(this.value = value.trim())) throw new Error('value must not be empty');
        Object.freeze(this);
    }

    toString() { return this.value; }
}

let username: Username = { value: '' };

When Username is used as a contract it should not only carry the promise of having a value: string member, but also to be immutable and to wrap a non-empty string (supporting a final keyword would also be necessary to avoid breaking encapsulation through inheritance). The fact that you can accidentally break encapsulation could be problematic.

Changing the implementation and have a private _value: string as well as a get value(): string getter would achieve a form of nominal typing naturally because different types with private members cannot be made equal, but supporting a nominal keyword which decorates interfaces & classes would be much better.

Can I confirm that this is still awaiting a proposal and that with a proposal this might move forwards? @Aleksey-Bykov 's contribution here looks interesting or does it need more refinement / improvement? To me though it's as "simple" as what @plalx said above:

structural type checking is used only where contracts are defined in terms of interfaces and nominal typing is used when contracts are defined in terms of concrete classes: concrete classes also makes the promise of an implementation which is a form of contract.

However if it was as "simple" as this it would have been implemented so I know I must be missing a lot of thinking around this simpler use case which would avoid very easy to make runtime bugs like #251.

Thanks :)

** edit ** I retract my previous statement and disagree with @plalx . Interfaces should be treated nominally as well. For example if you serialised an a JetPlane, you want not want to be able to use it to construct a chicken:

interface IChicken {
  id: number;
  name: string;
}
class Chicken implements IChicken {
  id: number;
  name: string;
  constructor(kwargs: IChicken) {
    this.id = kwargs.id;
    this.name = kwargs.name;
  }
}

interface IJetPlane {
  id: number;
  name: string;
}

var serialisedJetPlane: IJetPlane = {id: 1, name: 'Vulcan'};
var chick = new Chicken(serialisedJetPlane);  // should error

If you actually wanted to do that then perhaps you could cast: var chick = new Chicken(<IChicken> serialisedJetPlane);

Hi,
I really miss a nominal types in TypeScript as it would allow code DSLs and other stuff more easily. Now we cannot constrain API to allow only certain type, for example

type OrderId = string;

function processOrder(id:OrderId)

now any id/string can be passed to any API and we just get very little by doing that - no compile type checking.

How could be useful nominal typing can be seen in this excellent example of usage in Scala's Squant library: http://www.squants.com/

@antanas-arvasevicius

nominals are scheduled some time past 2.0: https://github.com/Microsoft/TypeScript/wiki/Roadmap

for now the only type that is somewhat nominal is enum, in your example it would be

const enum AsOrderId {}
type OrderId = string & AsOrderId;
let orderId = <OrderId> '123';
orderId = '124'; // <-- problem

I am saying somewhat because enum's coerce to numbers and back.

some languages have units of measure as a first class feature: https://msdn.microsoft.com/en-us/library/dd233243.aspx

we here hope to get them too long time: #364

Thank you Alexey! This should be enough for enforcing compiler to type check arguments.

Oh, didn't know that this kind of stuff already exists :)

@Aleksey-Bykov

your workaround seems to fail for the number type (but string, boolean, etc, works ok):

const enum AsOrderId {}
type OrderId = number & AsOrderId;
let orderId = <OrderId> 123;
orderId = 124; // <-- passes ok (but is wrong)

that's right, because numbers coerce to enums and enums coerce to numbers, in order to overcome this you might want consider the following:

module AsOrderId { export const enum Brand {} }
interface AsOrderId { '': AsOrderId.Brand; }
type OrderId = number & AsOrderId;
let orderId = <OrderId> 123;
orderId = 124; // <-- now complaints as expected

Here's a use case where I know I'll need nominal types for: number subtyping. In a performance-critical section of code for a project I'm intermittently working on, I need several different pointer types used with a reference-counting cache. Because of certain API hooks, I already have to do significant manual management, but mostly using numbers will help reduce memory dramatically and drastically increase speed for this.

Something like this, but imagine several files full of this madness:

// ./types.ts
export nominal Pointer extends number {}
export nominal TypePointer extends Pointer {}
export nominal AttrPointer extends Pointer {}
export nominal ChildrenPointer extends Pointer {}
export nominal ChildPointer extends Pointer {}
export interface Node {}

// ./caches.ts
import {AttrPointer, ChildrenPointer} from "./types"

class ReferenceCache<T, U extends number> {
  // properties
  function get(entry: U): T { /* whatever */ }
  function add(entry: T): U { /* whatever */ }
  function delete(entry: U): void { /* whatever */ }
}

export const attrCache = new ReferenceCache<any, AttrPointer>()
export const childCache = new ReferenceCache<Child[], ChildrenPointer>()

// ./patch.ts
import {attrCache, childCache} from "./caches"
import {Pointer, Node, AttrPointer, ChildrenPointer} from "./types"

function patch(nodes: Pointer[], start: i, node: Node) {
  const attrs = attrCache.get(<AttrPointer> nodes[i + 1])
  checkAttrs(attrs, node.attrs)

  const children = childCache.get(<ChildrenPointer> nodes[i + 2])
  const normalized = normalize(node.children)
  checkChildren(attrs, node.attrs)
}

I disagree with the chicken example by @AJamesPhillips, the distinction between classes and interfaces @plalx made is important.

If two interface declarations are structurally the same then they offer the same behaviour and should be treated the same. Isn't that the main point of interfaces? To implement a Toaster and a Mixer and have them satisfy the same IPowerSwitch interface? From the perspective of such an interface, a Toaster and a Mixer are the same thing. And if someone creates a second interface having the same behaviour then that means that it doesn't matter which one you use. Maybe I have this opinion because to me interfaces are first about behaviour and less about what something is.

@patrickjuchli BTW, some libraries return instances of classes and match with instanceof, but don't provide the constructor.

Although IMHO after #7642 gets addressed, the only desire I have for nominal typing is for opaque numeric subtypes (used as pointers) and types whose structure is purely an implementation detail.

I'd love to see if I can crack the nut of asm.js in TypeScript, but numeric subtypes are the only realistic way I could do that and keep types straight.

@patrickjuchli I think I've come round to that point of view. For "nominal" interfaces I'm using the following:

interface IJetPlane {
  IJetPlane: string;
  id: number;
  name: string;
  optional?: string;
}

It's slightly ungainly. It is a leaky implementation of nominal interfaces but serves it's purpose for my project using POJOs & Redux (I don't want to continuously go from class instances and back). Alternatively aleksey-bykov's comment is useful.

@patrickjuchli @AJamesPhillips

What I'm currently using is this (it's in a .d.ts file):

interface AddUpdate<T> extends Update {
  "update add": this;
  mask: nodes;
  index: number;
  child: Child<T>;
}

Granted, this would fare better with const enums also being permitted as types, but that or nominal types would be okay.

interface AddUpdate<T> extends Update {
  mask: nodes.ADD;
  index: number;
  child: Child<T>;
}

Note that nominal types will lead to surprising results with npm (node_modules) style commonjs modules. Nominal types that come from different versions of the same module will be different and incompatible (unless they are explicitly told what their unique name is).

Should be less of an issue with npm version 3.

By the way, I would expect nominal types to still match equivalent
structural interfaces. But I would mainly find nominal types useful for
keeping number subtypes straight. This varies from units in scientific
calculations to numeric pointers to types that their type is computed from
the result of a value (such as a combination of bitwise flags).

The last one requires dependent types (types dependent on their value),
which are probably not making it into TypeScript anytime soon, since it's
only just now getting past the purely academic and experimental world into
Haskell. It also makes a type system Turing complete, which a lot of
language writers don't usually like, by allowing type-level functions and
lambdas.

On Tue, Apr 12, 2016, 20:02 Gorgi Kosev notifications@github.com wrote:

Note that nominal types will lead to surprising results with npm
(node_modules) style commonjs modules (unless they are explicitly told what
their unique name is). Nominal types that come from different versions of
the same module will likely be different.

โ€”
You are receiving this because you commented.
Reply to this email directly or view it on GitHub
#202 (comment)

I need a nominal type for an opaque object whose structure is an implementation detail. I need this badly.

Intersecting with a branded interface seems pretty close to what we want to be possible:

> enum YearsBrand {}
undefined
> type Years = number & { _brand: YearsBrand }
undefined
> enum DaysBrand {}
undefined
> type Days = number & { _brand: DaysBrand }
undefined
> function yearsToDays(years: Years): Days { return <Days> years * 365; }
โจฏ Unable to compile TypeScript
[eval].ts (6,51): Neither type 'number & { _brand: YearsBrand; }' nor type 'number & { _brand: DaysBrand; }' is assignable to the other.
  Type 'number & { _brand: YearsBrand; }' is not assignable to type '{ _brand: DaysBrand; }'.
    Type '{ _brand: YearsBrand; }' is not assignable to type '{ _brand: DaysBrand; }'.
      Types of property '_brand' are incompatible.
        Type 'YearsBrand' is not assignable to type 'DaysBrand'. (2352)
[eval].ts (6,51): The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type. (2362)
[eval].ts (6,51): Type 'number' is not assignable to type 'number & { _brand: DaysBrand; }'.
  Type 'number' is not assignable to type '{ _brand: DaysBrand; }'. (2322)
> function yearsToDays(years: Years): Days { return <Days> (<number> years * 365); }
undefined
> yearsToDays(30)
โจฏ Unable to compile TypeScript
[eval].ts (7,13): Argument of type 'number' is not assignable to parameter of type 'number & { _brand: YearsBrand; }'.
  Type 'number' is not assignable to type '{ _brand: YearsBrand; }'. (2345)
> let age = <Years> 30
undefined
> yearsToDays(age)
10950

It would be nice if Typescript gave better errors and supported lifting operators, which implies a declaration syntax (I favor new type Foo = Bar to avoid more keywords, and it looks like the same feature in Haskell)

Being able to use them as keys would also be handy:

> enum UserIdBrand {}
undefined
> type UserId = string & { _brand: UserIdBrand }
undefined
> interface SessionCache { [userId: UserId]: any }
โจฏ Unable to compile TypeScript
[eval].ts (10,27): An index signature parameter type must be 'string' or 'number'. (1023)

@simonbuchan

That's the essence of nominal subtypes: sometimes you need numbers with formally correct unit handling, sometimes you need just an ordered, statically known set of numbers (that's enums of today), and sometimes you need to handle certain string keys as if they were their own type (where there are n possibilities, but they are more than just strings).

I've had a case where I need nominal interfaces for objects, because it would be an acceptable compromise to dependent types for a definition file. In my case, the object's type is dependent on the first few bits of a mask on the object itself, but since dependent types (types that depend on the value) won't likely exist in TypeScript for quite a while if ever, nominal interfaces with casting would work.

@isiahmeadows Yes? My comment was that Typescript already allows the creation of distinct (that is, nominal) types without changing the runtime shape (e.g. { ObjectId_value: string }), so the (real!) concern of "How do we avoid breaking the entire typesystem" can be approached as "How do we make this terrible hack on the type system less unusable", or in other words: if it would be broken with a new feature, it may already be broken! (In a bizzare edge-case, not a hopefully heavily used new feature)

Of course, you can get many of the same "benefits" with just class Foo {}; let x = <Foo><any> 123 - the intersect hack is only slightly safer without the extra <any> (and of course it doesn't generate any extra JS)

@simonbuchan I'd rather not even expose members that don't exist in the first place. That's why I try to avoid those hacks in the first place.

The V8 team is doing some experiments with SaneScript and SoundScript (in this order). Short description here where they also refer to TypeScript to be used as a basis.

There is also a presentation with detailed information. On pages 25 and following they talk about introducing nominal classes. Maybe these experiments could be helpful in this discussion.

A slightly generified version of @simonbuchan's state of the art for nominal types in TypeScript is:

/**
 * Nominal typing for any TypeScript interface or class.
 *
 * If T is an enum type, any type which includes this interface
 * will only match other types that are tagged with the same
 * enum type.
 */
interface Nominal<T> { 'nominal structural brand': T }

Usage is identical to his version (and the error messages are slightly better, in my opinion):

declare module Dates {
  export const enum Years {}
  export const enum Days {}
}

type Years = Nominal<Dates.Years> & number;
type Days = Nominal<Dates.Days> & number;

var day: Days;
var years: Years;
day = years;
// Type 'Nominal<Year> & number' is not assignable to type 'Nominal<Day> & number'.
//   Type 'Nominal<Year> & number' is not assignable to type 'Nominal<Day>'.
//     Type 'number' is not assignable to type 'Nominal<Day>'.

If it were possible to restrict the type of T in the Nominal interface to enum types this could be completely type-safe from the consumer's perspective.

A syntax proposal

Assuming that there was some way of restricting T to enum subtypes, the Nominal interface could be added as (for example) lang.Nominal and some syntactical sugar could be added for creating an interface or class that uses Nominal. I suggest the keyword distinct to tag a type which should be treated as a nominal type:

distinct interface SomeInterface { x: number, y: number, z: number }
distinct class SomeClass { a: string; b: string; c: string; }
distinct type SomeId = number

would be translated by the compiler (for ES5 and lower targets) as:

interface __SomeInterface  { x: number, y: number, z: number }
const enum __SomeInterfaceTag {}
type SomeInterface = __SomeInterface & lang.Nominal<__SomeInterfaceTag>

class SomeClass implements lang.Nominal<__SomeClassTag>  {
  'nominal structural brand': __SomeClassTag;
  a: string; b: string; c: string;
}
const enum __SomeClassTag {}

type SomeId = number & lang.Nominal<__SomeIdTag>;
const enum __SomeIdTag {};

the __-prefixed enums and interfaces being compiler-generated and inaccessible[1] to user code.

Usage

Casts would be necessary for any usage of a distinct type or distinct interface (but they are necessary for other languages too - such as the Scala shapeless library's Tagged utility). At it's simplest[2]:

// Types and interfaces just need to be "tagged"
var interfaceTagging = {x: 1, y: 1, z: 1} as SomeInterface;
var typeTagging = 123 as SomeId;

// Classes should be constructed specifically by the nominal constructor
var classCreation = new SomeClass('a', 'b', 'c');
// A cast is also possible, but at that point all type-safety is out the window.

Notes

  1. The compiler-generated enum is available to consuming code if it accesses the 'nominal structural brand' field and uses a type query. This could be mitigated by using a Symbol for the key.

  2. A specific keyword could be added for a "tagging" cast:

    var x = new Point(1, 2, 3) taggedAs SomeInterface;
    // Errors if SomeInterface is not a lang.Nominal

    I would argue for it since it documents the purpose of the cast and prevents using non-nominative types in nominative (i. e. "tagging") casts.

  3. @SimonMeskens points out an excellent ES2015+ approach that could be very useful as well - instead of using the string-keyed 'nominal structural brand' use Symbol.species for the brand key and the enum (or the type itself) for the type.

@svieira ๐Ÿ‘, but I'd like to point out that classes are already nominally typed, so the distinct keyword redundant for those cases.

@isiahmeadows - thanks!

Regarding classes being nominally typed, I may be reading section 8.1 wrong but I am pretty sure that they are still structurally typed:

The following example introduces both a named type called 'Point' (the class type) and a named value called 'Point' (the constructor function) in the containing declaration space.

class Point {  
    constructor(public x: number, public y: number) { }  
    public length() { return Math.sqrt(this.x * this.x + this.y * this.y); }  
    static origin = new Point(0, 0);  
}

The named type 'Point' is exactly equivalent to

interface Point {  
    x: number;  
    y: number;  
    length(): number;  
}

The named value 'Point' is a constructor function whose type corresponds to the declaration

var Point: {  
    new(x: number, y: number): Point;  
    origin: Point;  
};

@svieira Okay...that's news to me. I didn't know that. I went and checked in the playground, and you are correct. Still, ๐Ÿ‘ from me.

I just had an idea: if you modeled primitive types as nominal types extending interfaces (mod typeof narrowing, and assuming this isn't already the case), that could make this easier to do.

nominal boolean extends Boolean {}
nominal number extends Number {}
nominal string extends String {}
nominal symbol extends Symbol {}
// `void` is basically the same as `undefined | null`
// functions and objects use syntax instead.

(Note that primitives will need compiler help, though.)

@danquirk wrote:

Your latest example seems emblematic of some of my concerns. I take it you just want an error on the last line. But then in the same small snippet of code you've taken advantage of structural typing (a number is an Age since Age has no members) while also doing a nominal comparison a few lines later (to get an error using Height where Age is expected).

He could just as well have expressed the type as any, number, or string | number as desired. He wasn't forced to use structural typing, but probably did so as convenience. What is the concern specifically? Would you prefer that structural coercions are not allowed to be assigned to nominal types thus he would have been forced to declare the type?

Essentially every type comparison operation would require you to do the additional work of deducing whether the comparison was a structural or a nominal one, rather than having an easily predictable set of behavior.

When we compare any two instances of anything, we need to know their types to understand implications of implicit coercions (conversions) which don't even emit a compile-time error. How is it more difficult knowing whether only one of the types is nominal which will emit a compiler error if it is not?

For that scenario my preferred solution would be something like F#'s Units of Measure ( #364).

That seems to me to be the "kitchen sink" syntax pollution school-of-design. Nominal types can accomplish that and are the more general, generative essence construct.

@plalx wrote:

In my opinion, the default behavior should have been that structural type checking is used only where contracts are defined in terms of interfaces and nominal typing is used when contracts are defined in terms of concrete classes

Agreed. See below...

Changing the implementation and have a private _value: string as well as a get value(): string getter would achieve a form of nominal typing naturally because different types with private member properties cannot be made equal, but supporting a nominal keyword which decorates interfaces & classes would be much better.

Agreed. See below...

Note if we must decorate class, I would prefer nominal class instead of named class, because class can also be anonymous (nameless) so named is confusing. Also I would prefer a breaking change that is only turned on by flag where by default undecorated class is nominal and use interface for all structural typing.

@sampsyo wrote:

I'm using a void field as a workaround. It works nicely and doesn't get in the way.

If we make the unused field private, it doesn't appear in the JS output and it doesn't get in the way of structural casts if there is a matching member property:

class Bar {
    private _nominal:void
}

class Baz {
    value: number
    private _nominal:void
}
let x: Bar = <Bar>42,          // Error
    y: Bar = <Bar>{},          // Ok
    z: Baz = <Baz>{value: 42}  // Ok
y = z                          // Error

Since classes can also be used as interfaces, then that is also the way to create a nominal "interface", so we can leave interface for structural (and since interface can't have private member properties).

Tangentially, I'd prefer the abstract keyword is removed. When you don't provide an implementation for a member property, then it is abstract. Why the need to decorate it? Verbosity is code smell. Is it to be sure I didn't intend to provide an implementation? As if I can't see that I didn't, lol.

To nominally subtype the built-in nominal types, which I think is more self-documenting and less likely to produce an accidental collision that the recommendation currently in the FAQ:

class Nominal<T> {
    private _nominal:T
}
type Bar = number & Nominal<'Bar'> // employing string literal type
type Baz = number & Nominal<'Baz'>
let y: Bar = <Bar>42,         // Ok
    z: Baz = <Baz>53          // Ok
y = z                         // Error

So that now that we see TypeScript already effectively has nominal typing, I suppose the need to justify it's implementation is vacated.

Edit: afaics instanceof is always nominal and a sealed keyword would be required for exhaustive checking with a class subtyping nominal sum type if SPOT and DNRY are to be respected. Typeclasses would be an alternative to formalizing class nominal subtyping.

Edit#2: I wanted to change from private _nominal:void to private _nominal = undefined to make it explicit that no other class (not even a subclass given it's private not protected) is able to set the member property to any value, but this creates a property in the emitted output. In version 2.0, I anticipate this could be instead private _nominal: undefined, but I haven't tested it.

@shelby3

  1. Classes aren't nominal. Not in the slightest.

  2. The nominal subtyping mechanism you mention isn't really nominal. It's still structural; it's just using a string literal type to namespace it. With TypeScript 2.0, I believe you may be able to create nominal types with private opaque enum values like this:

    const enum Marker {Value}
    class Private { private _marker: Marker.Value }
    export type MyType = number & Private

    I'd recommend not trying to force nominal types into TypeScript in a way that requires frequent explicit casting in the meantime, though. If you focus on the data instead of the interface, you'd be surprised how far duck typing actually gets you.

@isiahmeadows wrote:

1โ€‹. Classes aren't nominal. Not in the slightest.

I never wrote that classes are nominal or providing any effectively nominal typing simulation by default. I hope you were not implying that I made such an incorrect assertion.

2โ€‹. The nominal subtyping mechanism you mention isn't really nominal. It's still structural

I didn't write that the nominal typing simulation isn't achieved with the structural type checker. The ability to enforce that every private or protected property is unique between nominal classes, is in effect a nominally associated typing feature. Also apparently instanceof remains nominal for instantiated classes.

I'd recommend not trying to force nominal types into TypeScript in a way that requires frequent explicit casting in the meantime, though. If you focus on the data instead of the interface, you'd be surprised how far duck typing actually gets you.

I intend to convince[1] that subclassing is a non-extensible anti-pattern and typeclasses is the superior paradigm for nominal typing.

[1] See the THE COVARIANCE OF GENERICS section and the link back to a TypeScript issue.

There is some evidence that TypeScript is conflating it design patterns around the separation-of-concepts for structural and nominal typing.

I hope we can get more organized.

Why is it nearly everyone wants to deny that the prototype chain is a feature of JavaScript that enables nominal typing (i.e. deny that JavaScript has nominal typing)?

And that nominal typing is not only subclassing?

@shelby3 Can you please elaborate how prototype would be a suitable candidate to solve nominal typing?

The example I have in mind is that my API returns the following JSON:

{
    "id": "ProductId",
    "name": "name"
}

Which then is deserialized to the following data structure:

interface Product {
    id: ProductId,
    name: string
}

The ProductId should be nominal typed. It's a string behind the scenes, but you can't just assign random strings to it. Same as you could not assign a UserId to it, even when that nominal type is also a string.

Since the data structure is deserialized I don't see how the prototype chain would work.

@shelby3

There is some evidence that TypeScript is conflating it design patterns around the separation-of-concepts for structural and nominal typing.

I hope we can get more organized.


Why is it nearly everyone wants to deny that the prototype chain is a feature of JavaScript that enables nominal typing (i.e. deny that JavaScript has nominal typing)?

And that nominal typing is not only subclassing?

  1. Please keep this civil. Accusing people of some supposed wrongdoing or speaking down to a group of people, isn't going to get you anywhere, even if you're right. This includes the above two comments, as well as both of the links to your comments from these two above ones.
  2. @spion in his comment did not in fact claim, much less imply, that nominal typing is not just subclassing. He was stating benefits of having nominally different structural types that still matched, using Thenables in the promise world as an example. This has nothing to do with subclassing, and is merely the fundamental difference between structural and nominal types.
  3. Yes, the prototype chain can be used for nominal typing. But is that useful in practice? It depends on the programmer. If you focus on the interface, then yes, nominal typing is a must. If you focus on the data, then nominal typing can actually get in the way at times. Call me crazy, but I'd rather have one way to define a simple point, not three different classes to define the same thing in different precisions (float, double, and int), extending a superclass which only matches the precision of one of them. Structural typing lets me instead return {x: 1, y: 2}, while descriptively naming the instance interface Point { x: number; y: number }. I tend to focus on the data, not the interface, so structural typing works fairly well. YMMV, though.

@isiahmeadows wrote:

Accusing people of some supposed wrongdoing or speaking down to a group of people

What group of people did I doxx? Please be specific and show proof.

The way I see it, I referred to a common mindset in the JS community-at-large, including myself in the recent past. Douglas Crockford even points out that we don't give prototype chain inheritance enough attention.

All the worrying about little nuances of intent is getting tiresome. It isn't a big problem. Our egos are not that fragile.

@spion in his comment did not in fact claim, much less imply, that nominal typing is not just subclassing.

Incorrect. Typeclasses can use nominal typing and they don't have the problem he was asserting is a problem with nominal typing. The problem he was referring to is caused by subclassing. Apparently you didn't understand this. So now you do.

Yes, the prototype chain can be used for nominal typing. But is that useful in practice?

You will know soon.

@shelby3 Both of these rhetorical questions come across as somewhat personal:

Why is it nearly everyone wants to deny that the prototype chain is a feature of JavaScript that enables nominal typing (i.e. deny that JavaScript has nominal typing)?

And that nominal typing is not only subclassing?

Both of these rhetorical questions come across as somewhat personal:

I repeat:

All the worrying about little nuances of intent is getting tiresome. Our egos are not that fragile.

I will not reply further on that topic. Thanks. (No disrespect intended, just as I say it is tiresome)

@shelby3, over the past few days you have had multiple posts on this forum that are antagonizing and disrespectful to other participants. This is not a comment to this specific issue, but rather a general one. This is a technical forum for discussing the TypeScript design and bugs, and not a political or social discussion group; and we do expect all participants to interact in a civil, respectful and productive manner.

This is not a request to explain your comments, or tone; this is rather a statement of how many others perceive them. If you are interested in participating in this forum, and have vested interest in future of the TypeScript community and JavaScript tooling in general, I would suggest you keep the discussion in technical terms, and keep the tone professional.

I'm opposed to solutions that create a construction that works purely compile-time. Typescript should attempt to type existing javascript as precisely and concisely as possible. The problem with nominal types is that they are not nominal at run-time, unless they are tagged objects. Typescript can already type tagged objects, though obviously, we are waiting for symbol support in computed properties to really leverage this feature.

Can anyone explain to me something that nominal types can do, that tagged objects don't do?

Looks like two worlds colliding here.. but in the reality there is no such thing as "nominal" in javascript, but it would be nice to have a compile time check for type mismatch in nominal way. Best example which would work for is if we had a lot of services with methods like update(id:SomethingId), update(id:OrderId), update(id:UserId) an such, and how to model API in a way that developers won't mix up these ids which all are "string", but meaning is different, and they should be able to pass any "string" in these methods if they explicitly cast it to appropriate type. Is that hard to implement in TS?

@antanas-arvasevicius this is already quite possible

// Use whatever magic string you like
type UserId = string & { "is user id": void }

at this point you can use any string as a UserId with a type assertion and everything basically works the way you'd expect. Technically this is still structural, though if everyone comes up with a unique magic string (not hard) it is effectively nominal.

@antanas-arvasevicius, as @RyanCavanaugh points out, Javascript has always had nominal types. Tagged objects are nominal if the tag is unique. By using a UUID as a property name, you ensure nominality. Even better is using the new ES2015 symbols, though Typescript can't properly type those yet (symbols in computed properties).

In fact, Javascript, as a prototypal language, provides a superset of classic OO, it is strictly MORE powerful. There isn't a single Java feature that Javascript can't do. The issue is: can Typescript express and typecheck the feature, which in case of nominal types, it can for UUIDs, but not for Symbols (yet).

interface Tagged {
    "d3a8e9b5-07c2-4a2d-a245-8a5908826b0f": void;
}

A handy semantic way is to use Symbol.species:

interface TaggedSpecies {
    [Symbol.species]: "d3a8e9b5-07c2-4a2d-a245-8a5908826b0f"
}

I haven't figured out a proper way to tag an object with multiple IDs yet when using the Symbol.species way, but I assume there's an elegant way to express it. Meanwhile, I suggest you just use the first sample I provided, which lets you tag as many IDs in an object as you want. Do note that for a robust code-base, you want to make sure that these tags are not enumerable, if you are actually adding them onto the object for runtime typechecking.

But that way it looks like some language hack and is strange to read ;) would be nice to have such supportiness in a language itself instead of making constructs like these every time. I don't know maybe that behavior is significant to minorities and will be rarely used then whatever.

You can fancy it up a little with string literal types

// Once
type Branded<T, U> = T & { '__ kind': U };

// Elsewhere
type OrderID = Branded<string, 'Order ID'>;

@antanas-arvasevicius It's not a hack, and it's widely used today. It may look strange, but what something like Java does, is add that same tag behind the scenes. I agree it might not look super elegant, but I tried suggesting a more elegant syntax and unfortunately, something like that is not within the current scope of Typescript.

@RyanCavanaugh You should probably use Symbol.species instead of __kind, which is spec compliant and more semantic:

// Once
type Branded<T, U> = T & { [Symbol.species]: U };

// Elsewhere
type OrderID = Branded<string, 'Order ID'>;

And remove the magic string maybe:

type OrderUUID = "d3a8e9b5-07c2-4a2d-a245-8a5908826b0f";

// Once
type Branded<T, U> = T & { [Symbol.species]: U };

// Elsewhere
type OrderID = Branded<string, OrderUUID>;
spion commented

@shelby3 I was not referring to subclassing. That was only the mechanism via which I illustrated the problem with nominal types.

Typeclasses only solve the initial step where you have to convince the owner to implement your nominal interface; you no longer need to do that - you can write your own instance. However, you still have to convince the first author as well as existing library writers to adopt your typeclass i.e. write their code as follows:

function f(x:TheNominalInterface) { ... }

rather then write their functions against the original nominal type written by the first author:

function f(x: TheNominalOriginalPromise) { ... }

In short, I believe that nominal types are not what you want by default, and it was brilliant of TypeScript to bring structural types to the mainstream. I also quite like the idea (its fresh, really) of a type system that doesn't put absolute type safety and soundness on a pedestal above all else, but gives it rational practical consideration with all other concerns.

@SimonMeskens Unfortunately these hack widely used workaround won't allow you to define a nominal alias for string, so that it can be used as an argument in an indexer.

@MartinJohns Not hacks or workarounds at all, as evidenced by the inclusion of Symbol into the standard. Strings are object like anything else, so it does work for strings too. You are correct they are not usable in indexers at present, which is a feature I could get behind (allow types that are either a subclass of string or a union with string in indexers). This is however a shortcoming of Typescript, not a shortcoming of tagged objects. It's also an issue easily fixed without adding new syntax to the language that creates a compile-time only construct. I was wrong, see comment further down

I get a bit tired when people call tagged objects hacks/workarounds. The literature disagrees with you and so does the standard.

@SimonMeskens By no means I want to be offensive or disrespectful by calling it a hack. It's merely my current understanding of the proposed solution, unless it gets further language support.

Am I understand this correctly that it would not only be a compile time thing, but also a runtime thing by adding a field to the JavaScript object? If yes, how would this play together with data structures parsed from, e.g. using JSON.parse()? The API backend surely would not include this brand information in the JSON. Then it also wouldn't be nominal typing, but still structural typing, wouldn't it?

@MartinJohns The last project I worked on, which was purely Javascript, already used this concept. The API backend did indeed send brand information, facilitated by the fact that it was running on .NET and thus had the type information needed to brand JSON sent to client. I started adding TypeScript annotations, precisely to leverage this existing behavior. If you are in the unfortunate situation of having to work with a backend that sends back unbranded objects, you would simply brand them yourself, as they come in. Separation of concerns dictates that you would already put your JSON.parse() call in the layer where you fetch the data, which also wraps the ajax calls, etc. In that same layer, you do structural integrity checks, to make sure the data received is valid, at that point, you can easily brand the objects if they prove to be valid.

Apologies for being a bit snippy about the use of the word hack, I just think that lots of developers shy away from using this pattern, because of a bias. This is probably not the place to discuss that though.

I think there are two different things being discussed here -- you can have branded types in TypeScript (using whatever property key you want since the property is not actually manifest at runtime) using intersection types or brand properties of an arbitrary property type. This is useful because you can brand things which can't actually have extra properties, such as strings and numbers.

A different problem is discriminating objects that are manifest, via some key. Here a discriminator property, usually with a string value, is well-supported.

I would call the first thing about a 4 on a 0-10 "hack" scale (anything 6 or above I would not even mention in this forum without being forced to; people who were trying to fake non-nullability with T | void scored an 8 or 9), the second thing scores a solid 0.

Fair enough, yeah, that's why I mentioned earlier I don't like the idea of forcing anything into the language that is not present at runtime (TypeScript should be descriptive of Javascript at all times imo). In case of tagging objects, I don't see why you wouldn't just tag the object for real, unless you are indeed forced to use a pre-existing framework that doesn't let you tag objects, something that does come up in real code.

@MartinJohns I made a mistake earlier, you are indeed correct that if you want to use these nominal strings for property names, that is not possible and it's not a feature TypeScript could ever support in the way described. You are mistaken in thinking that you tag the property names though, all suggestions here for branding through a tag are by making the value a unique literal and using TypeScript's excellent support for literal string types:

// this works!
interface Example {
    someTag: "unique string goes here"
}

By forcing any objects that satisfy this interface to have a tag with a unique value, you don't need the property name to be unique, and thus, indexers do not matter. The suggested semantic way I described uses [Symbol.species] as a property name, and a UUID as a value, to get both decent semantics and proper uniqueness.

My apologies for the confusion, I should've gone to bed quite a while ago.