Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Transparent to reverse Opaque #165

Closed
willium opened this issue Dec 7, 2020 · 19 comments · Fixed by #403
Closed

Add Transparent to reverse Opaque #165

willium opened this issue Dec 7, 2020 · 19 comments · Fixed by #403
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@willium
Copy link

willium commented Dec 7, 2020

Opaque nominal types are incredibly useful, but there are times where you need to un-opaque a type ignorer to use it (e.g. as a key in an object).

I propose the addition of

export type Transparent<T> = T extends Opaque<infer O, any> ? O : T

fails now:

type MyId = Opaque<string, "MyId">
type Nums = Record<MyId, number>

Element implicitly has an 'any' type because expression of type 'Opaque<string, "MyId">' can't be used to index type 'Record<Opaque<string, "MyId">, number>'

but this would fix that error without completely making Opaque types useless or overly compromising their contract:

type MyId = Opaque<string, "MyId">
type Nums = Record<Transparent<MyId>, number>
@sindresorhus
Copy link
Owner

Yeah, this sounds like a useful type to me.


Note that generalized index types are planned for TS 4.2, which might (did not test, just looked relevant) solve your specific problem: microsoft/TypeScript#26797

@sindresorhus
Copy link
Owner

// @voxpelli @kainiedziela Thoughts?

@voxpelli
Copy link
Collaborator

Makes sense to me 👍 Seems like a good addition, to be able to go both ways.

Is the name “Transparent” the best or would “DeOpaque”, “UnOpaque” or similar maybe show a clearer connection between the two and make it easier to find and understand + “pollute” the overall exports list a bit less? Even though such a wording might be linguistically less than ideal

@sindresorhus
Copy link
Owner

I was thinking about DeOpaque too for the same reason. Not as pretty as Transparent, but definitely clearer.

@willium
Copy link
Author

willium commented Dec 16, 2020

could do Translucent 😉

excited to see this land! and didn't know about the generalized type update in 4.2, looking forward to that!

@sindresorhus
Copy link
Owner

Some other alternatives:

  • OpaqueValue
  • OpaqueRawValue
  • UnwrapOpaque
  • OpaqueUnwrapped

@kainiedziela
Copy link
Contributor

I'm a fan of UnwrapOpaque, less of a tongue-twister than Un- or DeOpaque

@ulken
Copy link
Contributor

ulken commented Dec 16, 2020

If it ends up standing between Un or De, this might be worth considering: https://english.stackexchange.com/questions/25941/is-there-a-general-rule-for-the-prefixation-of-un-and-de-to-words

@willium
Copy link
Author

willium commented Dec 18, 2020

IMO the best naming convention would be to deprecate Opaque
and use one of the following:

  • Tag and Untag
  • Brand and Unbrand

@ulken
Copy link
Contributor

ulken commented Dec 18, 2020

I got it!

Unpaque™

As in unpacking an Opaque value.

@ulken
Copy link
Contributor

ulken commented Dec 18, 2020

@sindresorhus
Copy link
Owner

Then I suggest we go with Brand and Unbrand.

@voxpelli
Copy link
Collaborator

And maybe keep Opaque as an alias for a version or two? Adding @deprecated to it?

@sindresorhus
Copy link
Owner

Yup

@sindresorhus sindresorhus added enhancement New feature or request help wanted Extra attention is needed labels Dec 19, 2020
@sindresorhus
Copy link
Owner

sindresorhus commented Dec 28, 2020

Actually, maybe we could make the Opaque type actually opaque? And the existing implementation could be Brand.

I found this in some random TS playground:

declare const USERID_SYMBOL: unique symbol;
declare const ITEMID_SYMBOL: unique symbol;
declare const TAG_SYMBOL: unique symbol;

type Opaque<T, U extends symbol> = T & { readonly [TAG_SYMBOL]: U };
type UserId = Opaque<number, typeof USERID_SYMBOL>;
type ItemId = Opaque<number, typeof ITEMID_SYMBOL>;

Any thoughts on this approach?

Since it uses a unique symbol, it should be completely opaque as long as the symbol is not exposed. The TAG_SYMBOL part will require microsoft/TypeScript#26797, but we can just use __opaque__ for now.

@ulken
Copy link
Contributor

ulken commented Dec 28, 2020

Is there any difference between nominal and opaque types? microsoft/TypeScript#202

@ulken
Copy link
Contributor

ulken commented Dec 28, 2020

Actually, maybe we could make the Opaque type actually opaque? And the existing implementation could be Brand.

I found this in some random TS playground:

declare const USERID_SYMBOL: unique symbol;

declare const ITEMID_SYMBOL: unique symbol;

declare const TAG_SYMBOL: unique symbol;



type Opaque<T, U extends symbol> = T & { [TAG_SYMBOL]: U };

type UserId = Opaque<number, typeof USERID_SYMBOL>;

type ItemId = Opaque<number, typeof ITEMID_SYMBOL>;

Any thoughts on this approach?

Since it uses a unique symbol, it should be completely opaque as long as the symbol is not exposed. The TAG_SYMBOL part will require microsoft/TypeScript#26797, but we can just use __opaque__ for now.

Here's a discussion around all that: https://stackoverflow.com/a/56749647

Throw in a readonly also?

@sindresorhus
Copy link
Owner

sindresorhus commented Dec 28, 2020

Through microsoft/TypeScript#202, I also discovered another apporach for Brand (our current Opaque):

declare class Tagged<Token> {
	protected readonly __opaque__: Token;
}

export type Brand<Type, Token = unknown> = Type & Tagged<Token>;

The benefit here over our current approach is that the .__opaque__ property cannot be accessed here.

Not sure if we could make protected => private.

@sindresorhus
Copy link
Owner

Throw in a readonly also?

Sure

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants