Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
516 views
in Technique[技术] by (71.8m points)

How to define an opaque type in TypeScript?

If I recall correctly, in C++ you can define an an opaque type like this ...

class Foo;

... and use it like a handle e.g. when declaring function signatures ...

void printFoo(const Foo& foo);

Application code might then work with references-to-Foo or pointers-to-Foo without seeing the actual definition of Foo.

Is there anything similar in TypeScript -- how would you define an opaque type?

My problem is that if I define this ...

interface Foo {};

... then that's freely interchangeable with other similar types. Is there an idiom?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

That is because TypeScript type system is "structural", so any two types with the same shape will be assignable one to each other - as opposed to "nominal", where introducing a new name like Foo would make it non-assignable to a same-shape Bar type, and viceversa.

There's this long standing issue tracking nominal typings additions to TS.

One common approximation of opaque types in TS is using a unique tag to make any two types structurally different:

// opaque type module:
export type EUR = { readonly _tag: 'EUR' };
export function eur(value: number): EUR {
  return value as any;
}
export function addEuros(a: EUR, b: EUR): EUR {
  return ((a as any) + (b as any)) as any;
}

// usage from other modules:
const result: EUR = addEuros(eur(1), eur(10)); // OK
const c = eur(1) + eur(10) // Error: Operator '+' cannot be applied to types 'EUR' and 'EUR'.

Even better, the tag can be encoded with a unique Symbol to make sure it is never accessed and used otherwise:

declare const tag: unique symbol;
export type EUR = { readonly [tag]: 'EUR' };

Note that these representation don't have any effect at runtime, the only overhead is calling the eur constructor.

newtype-ts provides generic utilities for defining and using values of types that behave similar to my examples above.

Branded types

Another typical use case is to keep the non-assignability only in one direction, i.e. deal with an EUR type which is assignable to number:

declare const a: EUR;
const b: number = a; // OK

This can be obtained via so called "branded types":

declare const tag: unique symbol
export type EUR = number & { readonly [tag]: 'EUR' };

See for instance this usage in the io-ts library.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...