Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Nominalunique type brands#33038

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Closed
weswigham wants to merge1 commit intomicrosoft:mainfromweswigham:unique-types

Conversation

weswigham
Copy link
Member

@weswighamweswigham commentedAug 23, 2019
edited
Loading

Fixes#202
Fixes#4895

We've talked about this on and off for the last three years, and it was a major reason we chose to useunique symbol for the individual-symbol-type, since we wanted to reuse the operator for a nominal tag later. What this PR allows:

typeNormalizedPath= uniquestring;typeAbsolutePath= uniquestring;typeNormalizedAbsolutePath=NormalizedPath&AbsolutePath;declarefunctionisNormalizedPath(x:string):x isNormalizedPath;declarefunctionisAbsolutePath(x:string):x isAbsolutePath;declarefunctionconsumeNormalizedAbsolutePath(x:NormalizedAbsolutePath):void;constp="/a/b/c";if(isNormalizedPath(p)){if(isAbsolutePath(p)){consumeNormalizedAbsolutePath(p);}}

unique T (whereT is any type) is allowed in any position a type is allowed, and nominally tagsT with a marker that makes onlyT's that have come from that location be assignable to the resulting type.

This is done by adding a newNominalBrand type flag, which is a type with no structure which is unique to each symbol it is manufactured from. This is then mixed into the argument type tounique type via intersection, which is what produces all useful relationships. (The brand can have an alias if it is directly constructed viatype MyBrand = unique unknown)

This does so much with so little - this reduces the jankiness written into types to enable nominalness withunique symbols orenums, while addingzero new assignability rules.

So, why bring this up now? I was thinking about how "brands" work today, with something liketype MyBrand<T> = T & {[myuniquesym]: void} whereT could then become a literal type like"a". We've wanted, for awhile, to be able to more eagerly reduce an intersection of an object literal and a primitive tonever (to make subtype reduction and intersection reduction produce less jank and recognize more types as mutually exclusive), but these "brand" patterns keep stopping us. (Heck, we use em internally.) Well, if we ever want to change object types to actually meanobject, then we're going to need to provide an alternative for the brand pattern, and ideally that alternative needs to be available forawhile. So looking on the horizon to breaks we could take into 4.0 in 9 months, this simplification of branding would be up there, provided we've had the migration path available for awhile. So I'm trying to get the conversation started on this before we're too close to that deadline to plan something like that. Plus#202 is up there on our list of all-time most requested issues, and while we've always been open to it, we've never put forward a proposal of our own - well, here one is.

Onunique symbol

unique symbol's current behavior takes priority for syntactically exactlyunique symbol, however if a nominal subclass of symbols is actually desired, one can writeunique (symbol) to get a nominally brandedsymbol type instead (orsymbol & unique unknown - it's exactly the same thing). The way aunique symbol behaves is like a symbol that is locked to a specific declaration, and has special abilities when it comes to narrowing and control flow because of that. Nominally branded types are more flexible in their usage but do not have the strong control-flow linkage with a host statement thatunique symbols do (in fact, they don't necessarily assume a value exists at all), so there is very much reason for them to coexist. They'resimilar enough, that I'm pretty comfortable sharing syntax between the two.

Alternative considerations

While I've used theunique type operator here, like we've oft spoken of, on implementation, it's become plain to me thatI don't need to specify an argument tounique. We could just exposeunique as a unique type factory on it's own, and dispense with the indirection. The "uniqueness" we apply internally isn't actually tied to the input type argument through anything more than an intersection type, so simply shorteningunique unknown tounique and reserving the argument form for justunique symbols may be preferable. All the same patterns would be possible, one would just need to writestring & unique instead ofunique string, thus dispensing with the sugar. It depends on the perceived complexity, I think. However, despite beingexactly the same,string & unique is somehow uglier and harder to look at thanunique string, which is why I've kept it around for now. It's probably worth discussing, though.

What this draft would still need to be completed:

  • New error messages for any errors involving brands (One or more unique brands is missing from type A with related information pointing at the brand location, rather than the current error involvingunique unknown)
  • More tests exercising indexed access and exploring how indexed accesses on branded types are constructed (specifically, what should be done if you (attempt to) index nothing but a union ofunique unknown brands)
  • Tests forunique (symbol) declaration emit (to ensure it's not rewritten asunique symbol)
  • Discussion on ifkeyof <unique brand type> should benever (as it is now), since the brand is top-ish (and contains no structure information itself), or if it should be preserved as an abstractkeyof <unique brand> type, so that brand can apply keys-of-branded-types constraints in constraint positions
  • 🚲 🏠

cevek, yuhr, Igorbek, treybrisbane, be5invis, xiaoxiangmoe, eilvelia, sindresorhus, trotyl, blixt, and 261 more reacted with thumbs up emojiMelnCat, lexich, Newbie012, bolasblack, ivankirshin, richard-lopes-ifood, yuhr, eturchenkov, and 3swordman reacted with laugh emojicevek, yuhr, Igorbek, treybrisbane, be5invis, svieira, trotyl, bmakuh, xiaoxiangmoe, niieani, and 64 more reacted with hooray emojiyuhr, Igorbek, treybrisbane, xiaoxiangmoe, dderevjanik, trotyl, cevek, ckknight, bmakuh, leemhenson, and 77 more reacted with heart emojiyuhr, alexrock, Igorbek, treybrisbane, svieira, xiaoxiangmoe, dderevjanik, trotyl, cevek, chriskrycho, and 45 more reacted with rocket emojibrainkim, MelnCat, richard-lopes-ifood, Kadeluxe, eturchenkov, and 3swordman reacted with eyes emoji
@fatcerberus
Copy link

While I've used the unique type operator here, like we've oft spoken of, on implementation, it's become plain to me that I don't need to specify an argument to unique.

I don't understand this part. If these are meant to replace branded types, then:

typeUString= uniquestring;typeBString=string&{__brand:true};declareletustr:UString;declareletbstr:BString;letstr1:string=bstr;// ok because branded string is a subtype of string (this is generally desirable).letstr2:string=ustr;// could be ok because unique string is also a string.letnum:number=ustr;// never ok, should be error because unique string is NOT a number!

But if we just hadunique without the argument, there would be no way to make this distinction.unique would just end up being a nominalunknown (effectively an opaque Skolem constant) which doesn't sound that useful to me?

@weswigham
Copy link
MemberAuthor

But if we just had unique without the argument, there would be no way to make this distinction.

It's useful because you can then intersect it with something. Which is allunique string is doing under the hood right now - making that intersection for you.

@fatcerberus
Copy link

fatcerberus commentedAug 23, 2019
edited
Loading

Yeah, I need to read more closely. I just noticed thestring & unique bit before you posted. But I don't like this becauseunique by itselfisn't a type. It would be a special marker that doesn't work by itself as a type (or at least, doesn't make sense as one - what would it mean to take an argument of formarg: unique, e.g.), which I find very weird to then use in a position that nominally (no pun intended!) accepts only type operands. I guess there's precedent withThisType<T>, but... I don't know, it rubs me the wrong way.

It might be represented as an intersection under the hood, but that strikes me as unnecessarily exposing implementation details.

austincummings, hoeck, and ExE-Boss reacted with thumbs up emoji

@weswigham
Copy link
MemberAuthor

weswigham commentedAug 23, 2019
edited
Loading

unique is essentially just shorthand forunique unknown under the current model, and it does function fully as a standalone type.

@cevek
Copy link

cevek commentedAug 23, 2019
edited
Loading

Is this proposal allows to assign literals to variable/param which has branded type?

typeUserId= uniquestringfunctionfoo(param:UserId){}foo("foo")// okvarx:UserId="foo";// oklets="str";vary:UserId=s;// not okfoo(s)// not ok
papb and hen-x reacted with eyes emoji

@goodmind
Copy link

How would you make nominal classes with this?

@weswigham
Copy link
MemberAuthor

Not easily. You'd need to mix a distinct nominal brand into every class declaration, like via a property you don't use of typeunique unknown or similar, and then ensure that your inheritance trees always override such a field (so a parent and child don't appear nominally identical).

I'd avoid it, if possible, tbh. Nominal classes sound like a pain :P

@weswigham
Copy link
MemberAuthor

Is this proposal allows to assign literals to variable/param which has branded type?

Branded types can only be "made" either via cast or typeguard, as I said in the OP, so no. This is because the typesystem doesn't know what invariants a given brand is meant to maintain, and can't implicitly know if some literal satisfies them.

yuhr, raveclassic, aslatter, ExE-Boss, and hen-x reacted with thumbs up emoji

@jack-williams
Copy link
Collaborator

jack-williams commentedAug 23, 2019
edited
Loading

Would something like the following ever be meaningful? Probably not right..

interfaceParentextendsuniqueunknown{}interfaceChildAextends(Parent&uniqueunknown){}interfaceChildBextends(Parent&uniqueunknown){}

@AnyhowStep
Copy link
Contributor

AnyhowStep commentedAug 23, 2019
edited
Loading

This is probably obvious to everyone butunique T shouldn't replace branding.
There are scenarios where branding is the only viable solution (at the moment).

For example,

//Can be replaced with `unique number`typeLessThan256=number&{__rangeLt :256}//Cannot be replaced with `unique number`typeLessThan<Nextendsnumber>=number&{__rangeLt :N}

More complicated example here,
#15480 (comment)


There are a few reasons why I'm generally against the idea ofunique T.
(And nominal typing, andinstanceof, and using symbols)

Cross-library interop

Library A may havetype Radian = unique number.
Library B may havetype Radian = unique number.

Both types will be considered different, even though they have the same name and declaration.

If libraries start using thisunique T all over the place, you'll start needing unsafe casts (as libA.Radian) more often. This means you can accidentally writemyDegree as libA.Radian and cast incorrectly. Whoops!

So, one starts thinking that a no-op casting function would be safer,

functionlibARadianToLibBRadian(rad :libA.Radian) :libB.Radian{returnradaslibB.Radian;}

This is safer because you won't accidentally convertmyDegree:libA.Degree tolibB.Radian

But if you haveN libraries with their ownRadian type, you may end up needing up toN^2 functions to convert between each of theRadian types from each library.


Cross-version interop

It's happened to me a bunch where I've hadthe same package, but at different versions, within a single project.

So,

v1.0.0'stype Radian = unique number would be considered different from,
v1.1.0'stype Radian = unique number

Now you need a casting function... Even though it's the same package.


With brands, if two libraries use the same brands, even if they're different types, they'll still be assignable to each other. (As long as they don't useunique symbol)

Library A may havetype Rad = number & { __isRadian : void }.
Library B may havetype Radian = number & { __isRadian : void }.

Even though they're different types, they're assignable to each other. No casting needed.
v1.0.0 and v1.1.0 oftype Rad andtype Radian will work with each other fine.


As an aside, I vaguely remember something from many, many years ago. I can't find it through Google anymore, though.

There was discussion about adding syntax to C++ to maketypedef create anew type (rather than just functioning as a type alias),

typedefdouble Radian;

And this was rejected outright because of the issues I listed above.

Two libraries with their owntypedef double Radian; type would be incompatible, theN^2 problem, etc.

AlexeyMz, jeffpeterson, mickdekkers, denexapp, willfrew, wongjiahau, Raiondesu, and zbigg reacted with thumbs up emoji

@fatcerberus
Copy link

Re: nominal classes - classes arealready nominal if they contain any private members. Just throwing that out there. 😃

weswigham, dragomirtitian, tomblachut, pongo, LifeIsStrange, nicholasguyett, FeijianSun, omril1, and aml360 reacted with thumbs up emoji

@be5invis
Copy link

be5invis commentedAug 24, 2019
edited
Loading

So we canfinally have things like this?@weswigham

// Low-end refinement type :)typeNonEmptyArray<A>= uniqueReadonlyArray<A>functionisNonEmpty(a:ReadonlyArray<A>):a isNonEmptyArray<A>{returna.length>0}// INTEGERS (sort of)typeinteger= uniquenumberfunctionisInteger(a:number){returna===a|0}
xiaoxiangmoe, weswigham, sindresorhus, slorber, raveclassic, fuafa, samhh, Jerry-Hong, sethfowler, niieani, and jsoldi reacted with thumbs up emoji

@AnyhowStep
Copy link
Contributor

@fatcerberus It's also why I avoid classes entirely and avoid private members if I do have them =P

@AnyhowStep
Copy link
Contributor

AnyhowStep commentedAug 25, 2019
edited
Loading

Hmm...

constnormalizedPathBrand=Symbol();constabsolutePathBrand=Symbol();typeNormalizedPath=string&typeofnormalizedPathBrand;typeAbsolutePath=string&typeofabsolutePathBrand;typeNormalizedAbsolutePath=NormalizedPath&AbsolutePath;declarefunctionisNormalizedPath(x:string):x isNormalizedPath;declarefunctionisAbsolutePath(x:string):x isAbsolutePath;declarefunctionconsumeNormalizedAbsolutePath(x:NormalizedAbsolutePath):void;constp="/a/b/c";consumeNormalizedAbsolutePath(p);//Errorif(isNormalizedPath(p)){consumeNormalizedAbsolutePath(p);//Errorif(isAbsolutePath(p)){consumeNormalizedAbsolutePath(p);//OK}}

Playground


I guess the downside to this is that it's notactually a symbol.

ButSymbol doesn't have very methods one would accidentally use.
image

@jack-williams
Copy link
Collaborator

jack-williams commentedAug 26, 2019
edited
Loading

If cross version compatibility really is an issue then an alternate solution would be to make naming explicit:

Let the typeunknown "name" denote the set of all values with labelname. This would make the intersection type approach mandatory so a nominal path must now be written:

typeNormalizedPathOne=string& unique"NormalizedPath";

where an unlabelledunique denotes a generative type as defined in the OP.

typeNormalizedPathTwo=string& unique// some label that we don't care about that is auto-generated.

so while two declarations ofNormalizedPathTwo produce distinct types, two declarations ofNormalizedPathOne produce identical types.

FWIW I have no real preference---for me the big win of this feature is being able reduce empty intersections more aggressively.

Discussion on if keyof unique brand type ....

IMO, for all brand oblivious operations aunique type should be equivalent tounknown (which is what is proposed AFAIK).

ForbesLindesay, pimterry, nicholasguyett, hen-x, and jezierski-s reacted with thumbs up emoji

@resynth1943
Copy link

I've implemented Opaque types like so:

typeOpaque<V>=V&{readonly__opq__:unique symbol};typeAccountNumber=Opaque<number>;typeAccountBalance=Opaque<number>;functioncreateAccountNumber():AccountNumber{return2asAccountNumber;}functiongetMoneyForAccount(accountNumber:AccountNumber):AccountBalance{return4asAccountBalance;}getMoneyForAccount(100);// -> error

@AnyhowStep
Copy link
Contributor

@resynth1943

Your version breaks given the following,

typeOpaque<V>=V&{readonly__opq__:unique symbol};typeNormalizedPath=Opaque<string>;typeAbsolutePath=Opaque<string>;typeNormalizedAbsolutePath=NormalizedPath&AbsolutePath;declarefunctionisNormalizedPath(x:string):x isNormalizedPath;declarefunctionisAbsolutePath(x:string):x isAbsolutePath;declarefunctionconsumeNormalizedAbsolutePath(x:NormalizedAbsolutePath):void;constp="/a/b/c";consumeNormalizedAbsolutePath(p);//Errorif(isNormalizedPath(p)){consumeNormalizedAbsolutePath(p);//Expected Error, Actual OKif(isAbsolutePath(p)){consumeNormalizedAbsolutePath(p);//OK}}

Playground

Contrast with,
#33038 (comment)

@resynth1943
Copy link

@AnyhowStep I know, but that's how I'm currently creating opaque types. I hope this Pull Request will incorporate this into the language, and make it even better than my implementation.

@mohsen1
Copy link
Contributor

Nominal types are pretty useful. The example I often use is the APIs that take latitude/longitude and bugs that are result of mixing up latitude with longitude which are both numbers. By making those unique types we can avoid that class of bugs.

However, unique types can cause so much pain when you have to keep importing those types to simply use an API. So I'm hoping that at least primitive types are assignable to unique primitives where I can still call my functions like this:

// lib.tsexporttypeLat= uniquenumber;exporttypeLng= uniquenumber;exportfunctiondistance(lat:Lat,lng:Lng):number;// usage.tsimport{distance}from'lib.ts';distance(1234,5678);// no need to asset types

As@AnyhowStep mentioned cross-lib and cross-version conflicting unique types can also be a source of pain. Can we limit uniqueness scope somehow? Would that be a viable solution?

@weswighamweswigham mentioned this pull requestSep 6, 2019
@weswighamweswigham added the ExperimentA fork with an experimental idea which might not make it into master labelSep 6, 2019
@weswighamweswigham marked this pull request as ready for reviewSeptember 6, 2019 23:29
@weswigham
Copy link
MemberAuthor

#33290 is now open as well, so we can have a real conversation on what the non-nominal explicit tag would look like, and if we'd prefer it.

AnyhowStep, AndriiZelenskyi, and bolasblack reacted with heart emoji

@weswigham
Copy link
MemberAuthor

BTW, this is now a dueling features type situation (though I've authored both) - we won't accept both a#33290 style brand and this PR's style brand, only one of the two (and we're leaning towards#33290 on initial discussion). We'll get to debating it within the team hopefully during our next design meeting (next friday), but y'all should express ideas, preferences, and reasoning therefore within both PRs.

AnyhowStep, fatcerberus, karol-majewski, and JoePercy-Capp reacted with heart emoji

@weswigham
Copy link
MemberAuthor

Only repo contributors have permission to request that@typescript-bot pack this

AnyhowStep reacted with laugh emoji

@typescript-bot
Copy link
Collaborator

typescript-bot commentedSep 9, 2019
edited
Loading

Heya@weswigham, I've started to run the tarball bundle task on this PR at13968b0. You can monitor the buildhere. It should now contribute to this PR's status checks.

@typescript-bot
Copy link
Collaborator

Hey@weswigham, I've packed this intoan installable tgz. You can install it for testing by referencing it in yourpackage.json like so:

{    "devDependencies": {        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/43239/artifacts?artifactName=tgz&fileId=CC1A42393FB48F1DFF21222516C1DB28571C1E5134B16E3B234461EABAECAE1D02&fileName=/typescript-3.7.0-insiders.20190909.tgz"    }}

and then runningnpm install.

@xiaoxiangmoe
Copy link
Contributor

xiaoxiangmoe commentedSep 9, 2019
edited
Loading

typeinteger= uniquenumberfunctionisInteger(a:number):a isinteger{returna===(a|0)}functionInterger(a:number){if(!isInteger(a))thrownewError("not an integer");returna;}

@shicks
Copy link
Contributor

To what extent does#31894 satisfy the same requirements as this proposal? I know@DanielRosenwasser mentioned in#38510 that while nominal brands would be nice, but that it's not clear whether it's counter to the goals of 4.0 at the moment.

That said, it seems to me that one could easily enough write

placeholder type NormalizedPathBrand;placeholder type AbsolutePathBrand;type NormalizedPath = string & NormalizedPathBrand;type AbsolutePath = string & AbsolutePathBrand;

where the brands are module-local and never intended to be implemented by any concrete type. Branded types already need explicit casts to instantiate them, so it's easy enough to make them. Given that that proposal seems to have more traction, does it make this one obsolete?

@dead-claudia
Copy link

Is there a status update on this PR?

raveclassic, srdjan, huy-nguyen, yigitlevent, ivankirshin, chrisfls, safareli, ELLIOTTCABLE, polemius, epmatsw, and 21 more reacted with thumbs up emojikachkaev and btoo reacted with thumbs down emojiivankirshin, Jerry-Hong, evelant, instantepiphany, gabrielricardourbina, and negue reacted with heart emojiivankirshin, Jerry-Hong, evelant, gabrielricardourbina, negue, and joakim reacted with rocket emoji

@dead-claudia
Copy link

@weswigham Couple questions:

  1. Could you elaborate further on howunique symbol andunique (symbol) differ with respect to control flow-sensitive typing? I would've thought neither would have special behavior on that front.
  2. Do you have any status update for this? It's been over a year with pretty much radio silence.
treybrisbane, xiaoxiangmoe, raveclassic, shicks, samhh, and Arrow7000 reacted with thumbs up emojiraveclassic reacted with heart emoji

@weswigham
Copy link
MemberAuthor

Could you elaborate further on how unique symbol and unique (symbol) differ with respect to control flow-sensitive typing? I would've thought neither would have special behavior on that front.

unique symbol is asingle runtime value. Specifically, the result of an invocation ofSymbol() at some location. Because of this, it works as a well-known property name, and acts as a literal. Each placeunique symbol is written is a distinct, single symbol.unique (symbol) is "a unique subset of all symbols" and represents an abstract set of potentiallymultiple symbols. This behaves differently - notably, it isn't usable as well-known property name.

Do you have any status update for this? It's been over a year with pretty much radio silence.

Oh, wow, it's been that long. Uh. Last time I presented it to the team, the response was somewhat lukewarm - there's no real excitement or drive for it right now. So I guess what we're looking to see is overwhelming demand?

patricio-ezequiel-hondagneu-roig and ghoullier reacted with thumbs up emoji

@kachkaev
Copy link

kachkaev commentedOct 22, 2020
edited
Loading

So I guess what we're looking to see is overwhelming demand?

Not sure how the demand is properly measured, but this PR is intop 5 upvoted open PRs at the moment. Just saying 😁

samhh, makepanic, raveclassic, agos, ghoullier, yaroslavvoloshchuk-codaio, Arrow7000, dead-claudia, treybrisbane, SlurpTheo, and 20 more reacted with thumbs up emojiexpelledboy reacted with confused emoji

@dead-claudia
Copy link

@weswigham Thanks for the quick explanation! I see why both are necessary, now. Also, I definitely recommend looking into@kachkaev's comment above.

@alexweej
Copy link

Just used branding to very quickly identify the inconsistencies in a code base that was using string for 3 different logical dynamically valued types. Consider this my +1...

jrnail23, ExE-Boss, raveclassic, yuhr, ivankirshin, alarbada, timoxley, patricio-ezequiel-hondagneu-roig, richard-lopes-ifood, keysmashes, and 7 more reacted with thumbs up emoji

@ProdigySim
Copy link

ProdigySim commentedNov 30, 2021
edited
Loading

Just a bit of data: The last codebase I was in changed from usingunique symbol to string-based tag types due to their simplicity in creating compatible types across repos.

We wanted to move to a code generation system for our web API types. We had backend code that used nominal types ("tinytypes") in Scala which was the source of our branding.

Using unique-symbol based types had a couple of drawbacks here:

  1. We would need to have every nominal type exposed on the API to be predefined somewhere in TypeScript
  2. If the backend & frontend nominal type names mismatched at all, we would need to write custom mapping.
  3. One potential solution would be to have a shared library with all our "global" nominal types. But, this opens the possibility for "Dreaded Diamond" dependency patterns on these types, which would lead to incompatible nominal types (different "unique symbols" from different versions of the library)

We ended up converting all of our nominal types to string-based tag types overnight to support this project. They solved all of these problems:

  1. API code gen can just emit a genericOpaque<T, 'tag'> and not worry about references/imports
  2. If backend and frontend nomenclature mismatch, we can negotiate on the value of the'tag' string, without any user-facing implications.
  3. Don't need a shared library in this solution. If we did opt for one, the dreaded diamond would not be an issue unless the type tags actually changed--which would probably mark an important compatibility change.

tl;dr string-based tag types seem a little more flexible with implementation details and are still good enough. They still have the hard problem of naming things, but they are fixable when there are name conflicts. We can also put out recommendations for tag patterns in shared libraries.

jrnail23, hoeck, agos, and ethanresnick reacted with thumbs up emoji

@sandersn
Copy link
Member

This experiment is pretty old, so I'm going to close it to reduce the number of open PRs.

ljharb, negue, niedzielski, treybrisbane, btoo, cherryblossom000, diesal11, eps1lon, rn0, peternedap, and 14 more reacted with confused emoji

@negue
Copy link

Will there be any type of theseunique/opaque types or how ever these are called be added into typescript itself?

I needed something like that in a project where I had to juggle like a couple of simple "number" IDs and here something like unique number type per ID would've helped to save some time debugging :D

shicks, diesal11, eps1lon, ghoullier, rn0, raveclassic, and amikheychik reacted with thumbs up emoji

@shicks
Copy link
Contributor

Is there a particular reason this experiment stalled out?

cherryblossom000, diesal11, eps1lon, ghoullier, rn0, raveclassic, amikheychik, OmgImAlexis, and Ghabriel reacted with thumbs up emoji

@evelant
Copy link

evelant commentedMay 26, 2022
edited
Loading

@sandersn This is the 9th most voted on PR in the entire history of the repo, I think that's a clear indicator that a lot of people want it and it's probably worth pushing forward. Would you please reopen it?

IMO this is a very valuable feature. Nominal types can greatly increase type safety in any case where a value has a specific semantic meaning and thus should not be compatible with any similarly shaped type (or primitive) as is the default. Currently that's a difficult problem to solve with typescript. I would imagine nominal types could also greatly improve compiler performance by skipping structural comparisons.

IMO this should not be summarily closed simply because it hasn't had attention recently.

@weswigham since you authored this, do you think it is worth continuing the work you started or should this be closed in favor of a new discussion/implementation?

amikheychik, danitt, OmgImAlexis, jigargosar, Ghabriel, and a-x- reacted with thumbs up emoji

@weswigham
Copy link
MemberAuthor

I mean, personally, I think the structural version of this PR, thetag types PR, is the more general and better one. You can make those tags into nominal ones by including a unique symbol in your tag type, but conversely you can't make these into a structural tag, and structural tags have a lot of nice properties for interface merging and cross-library tag type management.

ProdigySim and shicks reacted with thumbs up emoji

@evelant
Copy link

@weswigham I'm not familiar with the PR you're referring to, would you link to it please?

@weswigham
Copy link
MemberAuthor

#33290

@evelant
Copy link

Thanks, I agree that PR seems like it is a better approach. Unfortunately it looks like that one got summarily closed as well. Given that there seems to be a lot of interest in support for some form of nominal typing perhaps it should be reopened?

wanton7, Hexagenic, fsenart, Naddiseo, cherryblossom000, jigargosar, Ghabriel, and fen-x reacted with thumbs up emoji

@sandersn
Copy link
Member

From the last comment on the other PR:

This is effectively an implementation sketch for#202. We'd prefer people interact with the suggestion issues than the PRs, since the implementation of a feature is mechanical once the design has been worked out, and nominal types are still very much under discussion

@shicksshicks mentioned this pull requestMar 17, 2023
@jcalzjcalz mentioned this pull requestJul 26, 2024
6 tasks
@ruojianll
Copy link

@weswigham Hey great man, how was this going?

dead-claudia reacted with thumbs up emojiNaddiseo reacted with thumbs down emojixiBread and IllusionMH reacted with confused emoji

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@mostasimmostasimmostasim approved these changes

@ahejlsbergahejlsbergAwaiting requested review from ahejlsberg

@RyanCavanaughRyanCavanaughAwaiting requested review from RyanCavanaugh

Assignees

@weswighamweswigham

Labels
ExperimentA fork with an experimental idea which might not make it into master
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

Tag types Support some non-structural (nominal) type matching
28 participants
@weswigham@fatcerberus@cevek@goodmind@jack-williams@AnyhowStep@be5invis@resynth1943@mohsen1@typescript-bot@xiaoxiangmoe@raveclassic@ProdigySim@joshburgess@mheiber@treybrisbane@ExE-Boss@michaeljota@Shou@shicks@dead-claudia@kachkaev@alexweej@sandersn@negue@evelant@ruojianll@mostasim

[8]ページ先頭

©2009-2025 Movatter.jp