Skip to content

Instantly share code, notes, and snippets.

@richsilv
Last active June 17, 2022 13:49
Show Gist options
  • Select an option

  • Save richsilv/bd1466107a88a35963417ccfc6ee4a69 to your computer and use it in GitHub Desktop.

Select an option

Save richsilv/bd1466107a88a35963417ccfc6ee4a69 to your computer and use it in GitHub Desktop.

Revisions

  1. richsilv revised this gist Jun 17, 2022. 1 changed file with 8 additions and 0 deletions.
    8 changes: 8 additions & 0 deletions new-type-example.ts
    Original file line number Diff line number Diff line change
    @@ -40,6 +40,10 @@ export namespace Thing {
    bar,
    } as Thing;
    }

    export updateFoo(thing: Thing, newFoo: number) {
    return Thing.MakeThing(thing.foo, Math.min(newFoo, thing.bar));
    }
    }

    // A function that requires the new type.
    @@ -69,6 +73,10 @@ class ThingClass {

    return new ThingClass(foo, bar);
    }

    public updateFoo(newFoo: number) {
    return new ThingClass(newFoo, Math.min(newFoo, this.bar));
    }
    }

    // A function that requires the new type.
  2. richsilv revised this gist Jun 16, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion new-type-example.ts
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@ interface Phantom<T> {
    }
    // The idea is that you narrow the type you're going
    // to use with a property which you're definitely not
    // going to be used, but that distinguishes this from
    // going to use, but that distinguishes this from
    // any old object which matches the same base interface.
    export type NewType<T, TagT> = T & Phantom<TagT>;

  3. richsilv revised this gist Jun 16, 2022. 1 changed file with 8 additions and 8 deletions.
    16 changes: 8 additions & 8 deletions new-type-example.ts
    Original file line number Diff line number Diff line change
    @@ -48,13 +48,13 @@ function logThing(thing: Thing) {
    console.log(thing.foo, thing.bar);
    }

    logThing({ foo: 10, bar: 5 });
    logThing({ foo: 10, bar: 5, __phantom: Symbol() } as const);
    logThing({ foo: 10, bar: 5 }); // TYPE ERROR
    logThing({ foo: 10, bar: 5, __phantom: Symbol() } as const); // TYPE ERROR
    // There is no way to pass type checking without either calling the
    // "constructor":
    logThing(Thing.makeThing(10, 5));
    // Or casting to the new type manually :(
    logThing({} as Thing);
    logThing(Thing.makeThing(10, 5)); // FINE
    // Or casting to the new type manually
    logThing({} as Thing); // FINE :(

    // ****************************************************

    @@ -77,6 +77,6 @@ function logThingClass(thing: ThingClass) {
    console.log(thing.foo, thing.bar);
    }

    logThing({ foo: 10, bar: 5 });
    logThingClass(ThingClass.makeThing(10, 5));
    logThingClass({} as ThingClass); // :(
    logThing({ foo: 10, bar: 5 }); // TYPE ERROR
    logThingClass(ThingClass.makeThing(10, 5)); // FINE
    logThingClass({} as ThingClass); // FINE :(
  4. richsilv revised this gist Jun 16, 2022. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions new-type-example.ts
    Original file line number Diff line number Diff line change
    @@ -8,6 +8,8 @@ interface Phantom<T> {
    // any old object which matches the same base interface.
    export type NewType<T, TagT> = T & Phantom<TagT>;

    // ****************************************************

    // An example base interface:
    interface ThingBase {
    readonly foo: number;
    @@ -54,6 +56,8 @@ logThing(Thing.makeThing(10, 5));
    // Or casting to the new type manually :(
    logThing({} as Thing);

    // ****************************************************

    // But note that this is exactly as type-safe as a class:
    class ThingClass {
    private constructor(readonly foo: number, readonly bar: number) {}
  5. richsilv created this gist Jun 16, 2022.
    78 changes: 78 additions & 0 deletions new-type-example.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,78 @@
    // Utility types (required only once):
    interface Phantom<T> {
    __phantom: T;
    }
    // The idea is that you narrow the type you're going
    // to use with a property which you're definitely not
    // going to be used, but that distinguishes this from
    // any old object which matches the same base interface.
    export type NewType<T, TagT> = T & Phantom<TagT>;

    // An example base interface:
    interface ThingBase {
    readonly foo: number;
    readonly bar: number;
    }

    // A non-reproduceable type to make your final type
    // non-reproduceable (this is safer than using a string).
    // Not exported!
    const THING_SYMBOL: unique symbol = Symbol();

    // Your resultant type.
    export type Thing = NewType<ThingBase, typeof THING_SYMBOL>;

    // eslint-disable-next-line @typescript-eslint/no-redeclare
    export namespace Thing {
    // A "constructor" for your type, which encodes the
    // required logic and casts to the new type.
    // You'd use similar free-functions with casts for
    // updates, etc.
    export function makeThing(foo: number, bar: number): Thing {
    if (foo <= bar) {
    throw new Error("Foo must be greater than bar!");
    }

    return {
    foo,
    bar,
    } as Thing;
    }
    }

    // A function that requires the new type.
    function logThing(thing: Thing) {
    // eslint-disable-next-line no-console
    console.log(thing.foo, thing.bar);
    }

    logThing({ foo: 10, bar: 5 });
    logThing({ foo: 10, bar: 5, __phantom: Symbol() } as const);
    // There is no way to pass type checking without either calling the
    // "constructor":
    logThing(Thing.makeThing(10, 5));
    // Or casting to the new type manually :(
    logThing({} as Thing);

    // But note that this is exactly as type-safe as a class:
    class ThingClass {
    private constructor(readonly foo: number, readonly bar: number) {}

    public static makeThing(foo: number, bar: number): ThingClass {
    if (foo <= bar) {
    throw new Error("Foo must be greater than bar!");
    }

    return new ThingClass(foo, bar);
    }
    }

    // A function that requires the new type.
    function logThingClass(thing: ThingClass) {
    // eslint-disable-next-line no-console
    console.log(thing.foo, thing.bar);
    }

    logThing({ foo: 10, bar: 5 });
    logThingClass(ThingClass.makeThing(10, 5));
    logThingClass({} as ThingClass); // :(