f# Readonly on Exports from Namespaces (#16720)
namespace N {
export class C { }
}
N.C = class {};
Array becomes invariant.ReadonlyArray.T and ReadonlyTreadonlykeyof T, you just write readonly T.Readonly mapped type, it makes all properties readonly as well.Readonly<T> mapped type doesn't eliminate mutating methods at all.This means we needed to add a way to indicate that a method doesn't mutate its object.
this: readonly syntax.Example:
class Foo {
name: string;
getName(this: readonly): string;
setName(value: string): void;
}
// This:
type ReadonlyFoo = readonly Foo;
// ... is equivalent to this:
type ReadonlyFoo = {
readonly name: string;
readonly getName(this: readonly): string;
}
What about deep readonly?
readonly Foo[] give you a readonly Array<readonly Foo>?immutable type operator--strictReadonly mode, this readonly operator would work as described above.readonly T not assignable to T.readonly props would not be assignable to mutable props.readonly T removes mutating methodsOutside of strictReadonly, readonly T is just Readonly<T>
All of this means that library authors would need to enable readonly mode.
Would TypeScript be able to infer whether a method is readonly or not?
How bad are strict variance modes without this?
super constraint for generics - otherwise many types stay invariant by virtue of taking a T as an input position.Will people know how to use this: readonly?
.d.ts files.readonly HTMLElement?How do this: readonly parameters work with overloads?
this: readonly, the property is eliminated.Since there's no issues linked to 'Readonly & Variance' section (you may want to link #1394, #10717), I'll leave a few comments here.
I'm wondering why a connection mentioned between variance and readability in that way. My understanding is that a type may be co- or contravariant on some other type it uses. Like a function is covariant on its parameter argument types and contravariant on return type. In case of array, it may be covariant to type of its elements, but it does not imply that it must be read-only.
I claim that in some cases, it is not obvious or even impossible to be express variance with readonly T. Consider:
interface Transformer<I, O> {
transform(value: I): O;
transformArray(values: I[]): O[];
}
What would readonly Transformer<I, O> mean? How is that useful for variance? More importantly is that Transformer is covariant on I and contravariant on O.
In #10717 I was proposing both co- and contra- varainces on generic types for both declaration and use sites:
interface Array<T> {
[index: number]: T;
push(value: T): void;
pop(): T;
}
type ReadonlyArray<T> = Array<out T>;
// is equivalent to this
interface ReadonlyArray<T> {
readonly [index: number]: T;
// push(value: T): void;
pop(): T;
}
type WriteonlyArray<T> = Array<in T>;
// is equivalent to this
interface WriteonlyArray<T> {
writeonly [index: number]: T; // no such syntax
push(value: T): void;
// pop(): T;
}
@Igorbek I've been thinking more about use-site variance recently as well. @ahejlsberg will be able to give more direct context, but I'll try answering your questions.
In general, the connection is that the readonly modifier removes every single method that isn't marked as this: readonly, which removes a great deal of methods that tend to make types invariant. While there's sometimes cases where generics in an input position don't necessarily modify contents of the containing type, this seems to be the case for a decent number of APIs. I believe the overall intent is that for many cases, the readonly modifier would make it significantly easier to manufacture a type that acts covariantly on its type parameters, which comes up quite a bit.
As an example, look at how we've had to create ReadonlyArray as a restricted counterpart for Array. You'd presumably want a ReadonlySet as well. Instead of declaring everything twice, we're exploring a more generalized mechanism.
To be clear, we are still experimenting with these ideas. It's not even clear whether stricter variance is something we're committed to, but I'm glad you asked.
Edit: I just noticed you pointed part of this out when you wrote:
In case of array, it may be covariant to type of its elements, but it does not imply that it must be read-only.
By the way, I want to point out that for many containers, a Foo<out T> is not necessarily read-only (imagine a method named clear()).
So neither readonly-ing a Foo<T> nor passing an upper-bounded existential type argument (Foo<out T>) fully subsumes the other.
Thank you @DanielRosenwasser for answering. That actually was my point that they, read-only-fity and covariance, are not always connected.
In my example, I just wanted to show something with current syntax. In _future_ syntax it would be something like:
interface Transformer<I, O> {
transform(value: I): O;
transformArray(values: in I[]): out O[];
}
// or, as a shortcut for the same
interface Transformer<in I, out O> {
transform(value: I): O;
transformArray(values: I[]): O[];
}
, or, in a more simplified case, I could just mark only I and let compiler do other work:
interface Transformer<I, O> {
transform(value: I): O;
transformArray(values: in I[]): O[];
}
declare const t1: Transformer<string, 'a'>;
const t2: Transformer<'b', 'a'> = t1; // ok
const o2 = t2.transformArray(['b']);
o2.push('a'); // ok
const t2: Transformer<string, string> = t1; // allowed, but! O is implicitly out
const o2 = t2.transformArray(['b']);
o2.push('b'); // error, method pushed is excluded
(kinda random thoughts)
Just 馃毑:house:ing, but while I know TypeScript has been sticking to any established JavaScript precedents, wherever available, when adopting type level syntax (extends/implements), C#'s in and out really are so much more expressive and easier to visually parse than <? super T> and <? extends T>, so if explicit variance annotations are ever added I think it might be nice to deviate and use in and out as @Igorbek does in his code examples above.
I propose that we continue the discussion of "Readonly & Variance" in #18770.
Most helpful comment
Just 馃毑:house:ing, but while I know TypeScript has been sticking to any established JavaScript precedents, wherever available, when adopting type level syntax (
extends/implements), C#'sinandoutreally are so much more expressive and easier to visually parse than<? super T>and<? extends T>, so if explicit variance annotations are ever added I think it might be nice to deviate and useinandoutas @Igorbek does in his code examples above.