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

Design Meeting Notes, 3/11/2022 #48226

Closed
DanielRosenwasser opened this issue Mar 11, 2022 · 6 comments
Closed

Design Meeting Notes, 3/11/2022 #48226

DanielRosenwasser opened this issue Mar 11, 2022 · 6 comments
Labels
Design Notes Notes from our design meetings

Comments

@DanielRosenwasser
Copy link
Member

Variance and Variance Annotations on Type Parameters

#48080 (comment)

#10717

(continued from #48209)

Playground Link

type Foo<T> = {
    x: T;
    f: FooFn<T>;
};

type FooFn<T> = (foo: Bar<T[]>) => void;

type Bar<T> = {
    value: Foo<T[]>;
};

// Try this in TypeScript 4.6.
// Whether or not you see an error on 'fn2 = fn1'
// depends on whether or not Section (B) comes before Section (A)

// Section (A)

declare let foo1: Foo<string>;
declare let foo2: Foo<unknown>;
foo1 = foo2;
foo2 = foo1;

// Section (B)

declare let fn1: FooFn<string>;
declare let fn2: FooFn<unknown>;

fn1 = fn2;
fn2 = fn1;
  • Could handle these better, but would break code.

  • Idea: variance annotations

    • Compare T by covariant rules:

      type Foo<out T> = {
          x: T;
          f: FooFn<T>;
      };
    • Compare T by contravariant rules:

      type Foo<in T> = {
          x: T;
          f: FooFn<T>;
      };
    • Compare T by invariant rules:

      type Foo<in out T> = {
          x: T;
          f: FooFn<T>;
      };
  • Can help with documentation, correctness, perf.

    • Perf results? 10-20% improvements because we have to measure variance.
    • This short-circuits any circularity.
  • Existing code that doesn't use these gets nothing out of it.

    • Should we emit these annotations in .d.ts files if we can accurately measure?
      • We can come up with states like bivariant, unmeasurable, and independent. Won't try to represent those. But for some subset (i.e. covariant, contravariant, invariant), we could do that.
      • But introducing these in declaration files would be a breaking change for downstream .d.ts consumers!
      • Start out by not doing this unless a user explicitly specified it.
  • Would be nice if we could surface this information in quick info though.

  • Interface merging combines the variance annotations.

    • Does that mean that interface emerging can invalidate the annotation of another interface?
      • Yes, but this was always the case. The compiler will issue an error and you'll manually have to fix that up.
  • Variance annotations on generic signatures we don't allow - doesn't make sense.

    • Doesn't it? We do a lot of work on this sort of thing.
  • Error messages?

    • Could special-case the last part.
    • Avoid saying "covariant" or "contravariant" - "input position" and "output position".
    • We report based on the marker types, using Super and Sub as the displayed names.
      • Those could be real names.
  • Who would benefit the most from the perf aspect here?

    • xstate, lodash, immutable, Ember?
    • Could use typesVersions to just give an instant perf boost to 4.7 users if this ships by then.
    • We don't have a way to measure "how much time is being spent in variance"
  • Maybe urge caution.

    • Lots of stuff that stops working if you start marking everything as in out (invariant).
  • Do we feel okay about the fact that two instantiations of the same type might be incompatible but structurally identical types would be compatible?

    • Yes-ish.
      • Usually the names of types are not material.
    • Really means we have to message this well.
    • Invariant is the only place where you can throw off how the type-checker is usually used.
      • Well if you say co/contravariant when we would've otherwise measured bivariant.
    • in out future-proofs the API - kinda nice.
  • So declaration emit?

    • No, not automatically. Breaking change for older versions.
  • This will force us to create a 4.7-based rift on DefinitelyTyped.

    • typesVersions?
    • If you use JSDoc, it's ignored by older versions.
    • Out of band file?
  • Should we have some sort of support for JSDoc here?

  • How does this play with "Types as Comments"?

    • Doesn't affect it, the proposal ignores content between < and >
  • JSDoc for variance annotations?

    • It might be the case that we need to lean on JSDoc for public, private, protected.
    • So in and out.
  • Our node factory APIs would need to take a breaking change.

    • We've done this, but people understandably are unhappy about it.
    • Optional parameters? Arity check won't work.
    • NodeFlags?
    • Overload that checks the first argument, and maybe deprecates the old overload?
  • Back to syntax!

    • So actual keywords?
      • Minimally yes, but also indecisive about back-compat and allowing /** @in @out */
@DanielRosenwasser DanielRosenwasser added the Design Notes Notes from our design meetings label Mar 11, 2022
@NN---
Copy link

NN--- commented Mar 16, 2022

Could you please think about an alternative syntax for invariant.
I think most developers would understand in out as in + out and not as not covariant and not contravariant.
Thanks.

@Conaclos
Copy link

Another possibility could be to adopt the flow syntax: + -.

@ShawnTalbert
Copy link

ShawnTalbert commented Apr 5, 2022

I agree with the above -which is also the Scala syntax for variance.

@RyanCavanaugh
Copy link
Member

We considered +/- but didn't like them compared to the keywords. in and out are well-precedented by Kotlin and C# and correspond directly to input and output positions, respectively. Even us on the team find the relationship between + / - and co/contra to be awkward to reason about; without an extremely strong theoretical background it's basically a rule you have to memorize, whereas the keywords are 1:1 with the use of the types.

@ritschwumm
Copy link

having been a heavy scala user for 15 years now, i have no trouble at all to work with + and -. still, in my opinion in and out are easier to read.

apart from that, they fit better into the general look and feel of typescript where a lot of things scala uses operators-like syntax for are expressed as keywords. extends comes to mind.

@Conaclos
Copy link

Conaclos commented Apr 8, 2022

I agree with the choice of the TypeScript team.
I was just hoping less fragmentation between Flow and TypeScript.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Notes Notes from our design meetings
Projects
None yet
Development

No branches or pull requests

6 participants