TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

add types for set methods proposal

Open bakkot opened this issue 1 year ago • 37 comments

Fixes #57228.

Set methods are stage 3, and I expect to ask for stage 4 in April.

I've used the simplest possible types here, but in some sense these are too restrictive. It's perfectly reasonable to ask if a Set of 0 | 1 is a subset of a Set of number, for example, or even of a Set of 1 | 2 - anything where the intersection of the types is nonempty (I guess that would be, any type S such that T & S extends never is false).

Unfortunately I don't know of a good way to express that constraint, at least not without making the types way more complex. So I've stuck with the simple thing here. But if someone has a suggestion for better types I'd happily take it.

One possibility would be to have the Set-producing types take SetLike<T> but the predicates take SetLike<unknown>. I don't know if that would be better.

Do note that this this PR introduces the SetLike interface.

bakkot avatar Jan 30 '24 06:01 bakkot

I guess that would be, any type S such that T & S extends never is false

I think this concept of "overlaps with" or its opposite "is disjoint with" is actually useful quite often. For example, it would be the ideal type of the parameter in [].includes(...). Currently I use this utility:

export const includes = <array extends readonly unknown[], element>(
	array: array,
	element: element & array[number] extends never ? array[number] : element
): element is element & array[number] => array.includes(element)

declare const array: string[]
declare const overlapping: string | number
declare const nonOverlapping: boolean | number

// okay
includes(array, overlapping)

// error
includes(array, nonOverlapping)

I wonder if the team would consider adding a builtin Overlaps type for cases like these and the set methods in this PR.

ssalbdivad avatar Jan 30 '24 13:01 ssalbdivad

Unfortunately I don't know of a good way to express that constraint, at least not without making the types way more complex. So I've stuck with the simple thing here. But if someone has a suggestion for better types I'd happily take it.

I'm not sure there's a good solution for this right now - it's more or less the same problem as #48247/#26255 and the fix is generally considered to be #14520 (lower-bounded type parameters)--though FWIW I think an "overlaps with" constraint would be more useful for this than "supertype of"

fatcerberus avatar Jan 30 '24 19:01 fatcerberus

@fatcerberus I definitely agree overlaps would be very useful in a number of circumstances like includes, as well as internally for a lot of these comparison errors that incorrectly assert the lack of an overlap (when really they're just checking for a downcast):

declare const a: { a: true }

declare const b: { b: true }

// '{ a: true; }' and '{ b: true; }' have no overlap
if (a === b) {
}

ssalbdivad avatar Jan 30 '24 21:01 ssalbdivad

Well, in reality any overlaps primitive we got would likely use the same internal check that produces the "these types don't overlap" error, so I don't think it would fix that problem by itself :wink:

fatcerberus avatar Jan 31 '24 05:01 fatcerberus

@fatcerberus Maybe it would be an opportunity to revisit that logic if it were to be published as its own utility 😅

ssalbdivad avatar Jan 31 '24 15:01 ssalbdivad

@typescript-bot pack this @typescript-bot test this @typescript-bot test top200 @typescript-bot user test this @typescript-bot run dt @typescript-bot perf test this

DanielRosenwasser avatar Jan 31 '24 20:01 DanielRosenwasser

Heya @DanielRosenwasser, I've started to run the parallelized Definitely Typed test suite on this PR at d9efe9618eab17785c35964dd44f0699ac01c5f7. You can monitor the build here.

Update: The results are in!

typescript-bot avatar Jan 31 '24 20:01 typescript-bot

Heya @DanielRosenwasser, I've started to run the diff-based top-repos suite on this PR at d9efe9618eab17785c35964dd44f0699ac01c5f7. You can monitor the build here.

Update: The results are in!

typescript-bot avatar Jan 31 '24 20:01 typescript-bot

Heya @DanielRosenwasser, I've started to run the diff-based user code test suite on this PR at d9efe9618eab17785c35964dd44f0699ac01c5f7. You can monitor the build here.

Update: The results are in!

typescript-bot avatar Jan 31 '24 20:01 typescript-bot

Heya @DanielRosenwasser, I've started to run the tarball bundle task on this PR at d9efe9618eab17785c35964dd44f0699ac01c5f7. You can monitor the build here.

typescript-bot avatar Jan 31 '24 20:01 typescript-bot

Heya @DanielRosenwasser, I've started to run the regular perf test suite on this PR at d9efe9618eab17785c35964dd44f0699ac01c5f7. You can monitor the build here.

Update: The results are in!

typescript-bot avatar Jan 31 '24 20:01 typescript-bot

Hey @DanielRosenwasser, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/159718/artifacts?artifactName=tgz&fileId=2BD1440F52B0CE1DB5E5082986F8EE22B10337C65AD4EDD247C29E5340509AFF02&fileName=/typescript-5.4.0-insiders.20240131.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/[email protected]".;

typescript-bot avatar Jan 31 '24 20:01 typescript-bot

The "overlaps" concept is what we internally call "comparability". The problem with expressing it as an intersection that produces never is that it is rarely possible to say an intersection of two object types is vacuous.

In other words, you can't say that { meow(): void } & { woof(): void } is an impossible type to fulfill, so there is no straightforward way to disallow catSet.intersection(dogSet).

DanielRosenwasser avatar Jan 31 '24 20:01 DanielRosenwasser

@DanielRosenwasser Here are the results of running the user test suite comparing main and refs/pull/57230/merge:

There were infrastructure failures potentially unrelated to your change:

  • 1 instance of "Package install failed"

Otherwise...

Something interesting changed - please have a look.

Details

puppeteer

packages/browsers/test/src/tsconfig.json

typescript-bot avatar Jan 31 '24 20:01 typescript-bot

Hey @DanielRosenwasser, the results of running the DT tests are ready. Everything looks the same! You can check the log here.

typescript-bot avatar Jan 31 '24 20:01 typescript-bot

@DanielRosenwasser The results of the perf run you requested are in!

Here they are:

tsc

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Angular - node (v18.15.0, x64)
Memory used 295,662k (± 0.01%) 295,671k (± 0.01%) ~ 295,637k 295,725k p=0.810 n=6
Parse Time 2.66s (± 0.31%) 2.67s (± 0.19%) +0.01s (+ 0.38%) 2.66s 2.67s p=0.050 n=6
Bind Time 0.83s (± 1.52%) 0.82s (± 0.63%) ~ 0.82s 0.83s p=0.418 n=6
Check Time 8.19s (± 0.47%) 8.21s (± 0.34%) ~ 8.18s 8.26s p=0.870 n=6
Emit Time 7.10s (± 0.25%) 7.10s (± 0.23%) ~ 7.08s 7.12s p=1.000 n=6
Total Time 18.78s (± 0.30%) 18.80s (± 0.15%) ~ 18.78s 18.86s p=1.000 n=6
Compiler-Unions - node (v18.15.0, x64)
Memory used 193,514k (± 1.59%) 192,944k (± 1.25%) ~ 191,491k 197,365k p=0.689 n=6
Parse Time 1.35s (± 0.87%) 1.36s (± 1.76%) ~ 1.32s 1.38s p=0.220 n=6
Bind Time 0.72s (± 0.00%) 0.72s (± 0.00%) ~ 0.72s 0.72s p=1.000 n=6
Check Time 9.38s (± 0.50%) 9.41s (± 0.37%) ~ 9.36s 9.44s p=0.469 n=6
Emit Time 2.62s (± 0.66%) 2.61s (± 1.40%) ~ 2.55s 2.66s p=0.570 n=6
Total Time 14.08s (± 0.38%) 14.10s (± 0.37%) ~ 14.00s 14.14s p=0.332 n=6
Monaco - node (v18.15.0, x64)
Memory used 347,460k (± 0.00%) 347,465k (± 0.00%) ~ 347,442k 347,478k p=0.336 n=6
Parse Time 2.48s (± 0.44%) 2.48s (± 0.36%) ~ 2.47s 2.49s p=1.000 n=6
Bind Time 0.93s (± 0.68%) 0.93s (± 0.44%) ~ 0.92s 0.93s p=0.673 n=6
Check Time 6.96s (± 0.56%) 6.96s (± 0.55%) ~ 6.92s 7.03s p=1.000 n=6
Emit Time 4.07s (± 0.45%) 4.06s (± 0.34%) ~ 4.04s 4.08s p=0.241 n=6
Total Time 14.44s (± 0.36%) 14.43s (± 0.32%) ~ 14.38s 14.50s p=0.809 n=6
TFS - node (v18.15.0, x64)
Memory used 302,858k (± 0.01%) 302,858k (± 0.01%) ~ 302,832k 302,894k p=0.689 n=6
Parse Time 2.01s (± 0.86%) 2.02s (± 0.68%) ~ 1.99s 2.03s p=0.934 n=6
Bind Time 1.01s (± 1.04%) 1.00s (± 1.22%) ~ 0.99s 1.02s p=1.000 n=6
Check Time 6.33s (± 0.41%) 6.34s (± 0.34%) ~ 6.31s 6.37s p=0.624 n=6
Emit Time 3.59s (± 0.45%) 3.59s (± 0.42%) ~ 3.57s 3.61s p=1.000 n=6
Total Time 12.94s (± 0.21%) 12.96s (± 0.19%) ~ 12.92s 12.98s p=0.515 n=6
material-ui - node (v18.15.0, x64)
Memory used 511,313k (± 0.00%) 511,325k (± 0.01%) ~ 511,290k 511,384k p=0.748 n=6
Parse Time 2.65s (± 0.66%) 2.65s (± 0.98%) ~ 2.62s 2.68s p=0.935 n=6
Bind Time 0.99s (± 1.04%) 1.00s (± 0.98%) ~ 0.99s 1.01s p=0.203 n=6
Check Time 17.27s (± 0.37%) 17.28s (± 0.40%) ~ 17.19s 17.37s p=0.936 n=6
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) ~ 0.00s 0.00s p=1.000 n=6
Total Time 20.90s (± 0.26%) 20.92s (± 0.33%) ~ 20.83s 21.03s p=0.685 n=6
mui-docs - node (v18.15.0, x64)
Memory used 1,695,135k (± 0.00%) 1,695,118k (± 0.00%) ~ 1,695,074k 1,695,186k p=0.199 n=6
Parse Time 6.53s (± 0.29%) 6.52s (± 0.38%) ~ 6.49s 6.56s p=0.466 n=6
Bind Time 2.36s (± 0.22%) 2.36s (± 0.35%) ~ 2.34s 2.36s p=0.114 n=6
Check Time 55.43s (± 0.37%) 55.49s (± 0.33%) ~ 55.24s 55.66s p=0.687 n=6
Emit Time 0.16s (± 2.52%) 0.16s (± 2.52%) ~ 0.16s 0.17s p=1.000 n=6
Total Time 64.48s (± 0.34%) 64.53s (± 0.30%) ~ 64.26s 64.71s p=0.810 n=6
self-build-src - node (v18.15.0, x64)
Memory used 2,410,763k (± 0.04%) 2,410,667k (± 0.03%) ~ 2,409,469k 2,411,451k p=1.000 n=6
Parse Time 4.93s (± 0.99%) 4.94s (± 0.59%) ~ 4.89s 4.98s p=0.575 n=6
Bind Time 1.88s (± 0.27%) 1.88s (± 1.08%) ~ 1.87s 1.92s p=0.277 n=6
Check Time 33.45s (± 0.23%) 33.46s (± 0.35%) ~ 33.26s 33.58s p=0.810 n=6
Emit Time 2.69s (± 1.90%) 2.70s (± 1.91%) ~ 2.64s 2.78s p=0.810 n=6
Total Time 42.98s (± 0.17%) 42.99s (± 0.30%) ~ 42.76s 43.15s p=0.335 n=6
self-compiler - node (v18.15.0, x64)
Memory used 418,598k (± 0.00%) 418,659k (± 0.02%) ~ 418,588k 418,780k p=0.230 n=6
Parse Time 2.79s (± 2.23%) 2.74s (± 3.78%) ~ 2.66s 2.87s p=0.569 n=6
Bind Time 1.10s (± 5.44%) 1.17s (± 6.50%) ~ 1.07s 1.23s p=0.112 n=6
Check Time 15.10s (± 0.36%) 15.10s (± 0.34%) ~ 15.03s 15.16s p=1.000 n=6
Emit Time 1.14s (± 1.11%) 1.14s (± 1.29%) ~ 1.12s 1.16s p=0.869 n=6
Total Time 20.13s (± 0.27%) 20.15s (± 0.34%) ~ 20.07s 20.24s p=0.575 n=6
vscode - node (v18.15.0, x64)
Memory used 2,811,213k (± 0.00%) 2,811,228k (± 0.00%) ~ 2,811,192k 2,811,278k p=0.748 n=6
Parse Time 10.62s (± 0.38%) 10.61s (± 0.37%) ~ 10.56s 10.65s p=0.808 n=6
Bind Time 3.42s (± 0.55%) 3.41s (± 1.15%) ~ 3.39s 3.49s p=0.413 n=6
Check Time 59.81s (± 0.67%) 60.09s (± 0.57%) ~ 59.62s 60.54s p=0.378 n=6
Emit Time 16.14s (± 0.57%) 16.16s (± 0.62%) ~ 16.00s 16.29s p=0.628 n=6
Total Time 89.99s (± 0.38%) 90.27s (± 0.43%) ~ 89.72s 90.75s p=0.230 n=6
webpack - node (v18.15.0, x64)
Memory used 393,456k (± 0.01%) 393,471k (± 0.02%) ~ 393,354k 393,639k p=0.936 n=6
Parse Time 3.08s (± 0.65%) 3.07s (± 0.74%) ~ 3.04s 3.11s p=0.492 n=6
Bind Time 1.41s (± 0.39%) 1.39s (± 1.33%) ~ 1.37s 1.41s p=0.341 n=6
Check Time 14.00s (± 0.30%) 14.03s (± 0.34%) ~ 13.94s 14.06s p=0.171 n=6
Emit Time 0.00s (± 0.00%) 0.00s (±244.70%) ~ 0.00s 0.01s p=0.405 n=6
Total Time 18.48s (± 0.21%) 18.49s (± 0.20%) ~ 18.43s 18.52s p=0.627 n=6
xstate - node (v18.15.0, x64)
Memory used 513,374k (± 0.01%) 513,407k (± 0.00%) ~ 513,381k 513,432k p=0.065 n=6
Parse Time 3.27s (± 0.36%) 3.28s (± 0.30%) ~ 3.27s 3.29s p=0.314 n=6
Bind Time 1.54s (± 0.49%) 1.54s (± 0.49%) ~ 1.53s 1.55s p=1.000 n=6
Check Time 2.84s (± 0.87%) 2.85s (± 0.91%) ~ 2.81s 2.88s p=0.685 n=6
Emit Time 0.08s (± 4.99%) 0.08s (± 0.00%) ~ 0.08s 0.08s p=0.405 n=6
Total Time 7.73s (± 0.29%) 7.74s (± 0.47%) ~ 7.70s 7.79s p=0.518 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • Angular - node (v18.15.0, x64)
  • Compiler-Unions - node (v18.15.0, x64)
  • Monaco - node (v18.15.0, x64)
  • TFS - node (v18.15.0, x64)
  • material-ui - node (v18.15.0, x64)
  • mui-docs - node (v18.15.0, x64)
  • self-build-src - node (v18.15.0, x64)
  • self-compiler - node (v18.15.0, x64)
  • vscode - node (v18.15.0, x64)
  • webpack - node (v18.15.0, x64)
  • xstate - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

tsserver

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-UnionsTSServer - node (v18.15.0, x64)
Req 1 - updateOpen 2,345ms (± 0.43%) 2,342ms (± 0.75%) ~ 2,330ms 2,377ms p=0.227 n=6
Req 2 - geterr 5,546ms (± 1.36%) 5,548ms (± 1.54%) ~ 5,419ms 5,620ms p=0.936 n=6
Req 3 - references 323ms (± 0.30%) 325ms (± 1.56%) ~ 321ms 332ms p=0.742 n=6
Req 4 - navto 274ms (± 1.45%) 275ms (± 1.32%) ~ 271ms 279ms p=1.000 n=6
Req 5 - completionInfo count 1,356 (± 0.00%) 1,356 (± 0.00%) ~ 1,356 1,356 p=1.000 n=6
Req 5 - completionInfo 87ms (± 8.43%) 90ms (± 7.40%) ~ 84ms 101ms p=0.466 n=6
CompilerTSServer - node (v18.15.0, x64)
Req 1 - updateOpen 2,479ms (± 1.56%) 2,483ms (± 1.23%) ~ 2,448ms 2,535ms p=0.689 n=6
Req 2 - geterr 4,197ms (± 2.07%) 4,181ms (± 1.78%) ~ 4,123ms 4,279ms p=0.630 n=6
Req 3 - references 336ms (± 1.77%) 340ms (± 1.61%) ~ 331ms 346ms p=0.568 n=6
Req 4 - navto 284ms (± 0.53%) 285ms (± 1.19%) ~ 281ms 291ms p=0.466 n=6
Req 5 - completionInfo count 1,518 (± 0.00%) 1,518 (± 0.00%) ~ 1,518 1,518 p=1.000 n=6
Req 5 - completionInfo 80ms (± 6.65%) 85ms (± 8.23%) ~ 74ms 90ms p=0.106 n=6
xstateTSServer - node (v18.15.0, x64)
Req 1 - updateOpen 2,598ms (± 0.57%) 2,612ms (± 0.44%) ~ 2,594ms 2,626ms p=0.126 n=6
Req 2 - geterr 1,736ms (± 2.23%) 1,731ms (± 2.70%) ~ 1,680ms 1,783ms p=1.000 n=6
Req 3 - references 116ms (± 9.63%) 119ms (± 8.24%) ~ 107ms 128ms p=0.466 n=6
Req 4 - navto 370ms (± 0.41%) 371ms (± 0.56%) ~ 369ms 375ms p=1.000 n=6
Req 5 - completionInfo count 2,078 (± 0.00%) 2,078 (± 0.00%) ~ 2,078 2,078 p=1.000 n=6
Req 5 - completionInfo 311ms (± 1.57%) 311ms (± 2.40%) ~ 301ms 319ms p=0.873 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • CompilerTSServer - node (v18.15.0, x64)
  • Compiler-UnionsTSServer - node (v18.15.0, x64)
  • xstateTSServer - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

startup

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
tsc-startup - node (v18.15.0, x64)
Execution time 153.16ms (± 0.20%) 152.79ms (± 0.18%) -0.37ms (- 0.24%) 151.87ms 157.17ms p=0.000 n=600
tsserver-startup - node (v18.15.0, x64)
Execution time 229.03ms (± 0.16%) 228.91ms (± 0.15%) -0.12ms (- 0.05%) 227.67ms 232.49ms p=0.000 n=600
tsserverlibrary-startup - node (v18.15.0, x64)
Execution time 231.42ms (± 0.19%) 231.30ms (± 0.18%) -0.12ms (- 0.05%) 229.33ms 238.19ms p=0.031 n=600
typescript-startup - node (v18.15.0, x64)
Execution time 230.78ms (± 0.19%) 230.77ms (± 0.18%) ~ 229.27ms 236.83ms p=0.987 n=600
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • tsc-startup - node (v18.15.0, x64)
  • tsserver-startup - node (v18.15.0, x64)
  • tsserverlibrary-startup - node (v18.15.0, x64)
  • typescript-startup - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6
Developer Information:

Download Benchmarks

typescript-bot avatar Jan 31 '24 20:01 typescript-bot

@DanielRosenwasser Here are the results of running the top-repos suite comparing main and refs/pull/57230/merge:

Everything looks good!

typescript-bot avatar Jan 31 '24 22:01 typescript-bot

@DanielRosenwasser Yes, that is exactly the behavior I'd expect in a structural type system, with a result of Set<{meow(): void; woof(): void}>. The type could still give completions based on the base type which must be overlapped, but this kind of intersection is definitely sensible as illustrated by an alternate example like { poisonous: true } & { mammalian: true } for a set of platypuses and echidnas 😄

I'm often frustrated by TS overreaching based on a comparability check (e.g. https://github.com/microsoft/TypeScript/issues/44645), especially when the word "overlap" is used in the error message which has a specific meaning that does not match the criteria for the error.

ssalbdivad avatar Jan 31 '24 22:01 ssalbdivad

I brought this up in the design meeting, specifically focusing on

  1. the types of union/intersection
  2. the parameter container types.

The feedback for them was:

  1. Methods like union and intersection should be generic and return a corresponding TypeScript type. union should return a Set<T | U>, and intersection should return a Set<T & U>.

  2. Methods should not expect their arguments to be related to the underlying container type. This risks accidental invocations of intersection that are guaranteed to produce empty collections, but it has the benefit of allowing more symmetric operations.

    In other words, both stringSet.intersection(stringOrBooleanSet) and stringOrBooleanSet.intersection(stringSet) are valid invocations of each other.

    I'll note that I am personally the most opposed to this behavior, but I was the lone dissenter. 🙂

  3. With respect to SetLike, @rbuckton raised the same points of ReadonlySet being overkill. I suggested ReadonlySetLike might make more sense. Nobody else had a strong opinion.

@bakkot, in this PR I'd suggest to just address the first two points, do whatever you feel is right for point 3, and we will bikeshed by the time we can bring this in for TypeScript 5.5.

DanielRosenwasser avatar Jan 31 '24 23:01 DanielRosenwasser

WFM, thanks for the feedback. I'll get that pushed up later today.

Any opinions on isSubsetOf and friends? If not I'll default to making them generic, following point 2, even though (because they return booleans) this doesn't let you constrain the output.

bakkot avatar Jan 31 '24 23:01 bakkot

Any opinions on isSubsetOf and friends? If not I'll default to making them generic

I think the right thing for those is to take a Set<unknown> since the type parameter will never be observed.

DanielRosenwasser avatar Jan 31 '24 23:01 DanielRosenwasser

In other words, both stringSet.intersection(stringOrBooleanSet) and stringOrBooleanSet.intersection(stringSet) are valid invocations of each other.

This is generally what people expect of e.g. Array.prototype.includes as well, and I largely concur, but to my knowledge there's no way to allow that without also allowing stringSet.intersection(numberSet) (unless we get lower-bounded or comparability constraints), correct?

fatcerberus avatar Feb 01 '24 06:02 fatcerberus

@DanielRosenwasser Done. Specifically:

union<U>(other: ReadonlySetLike<U>): Set<T | U>; // things in result are in either
intersection<U>(other: ReadonlySetLike<U>): Set<T & U>; // things in result are in both
difference<U>(other: ReadonlySetLike<U>): Set<T>; // things in result are in receiver
symmetricDifference<U>(other: ReadonlySetLike<U>): Set<T | U>; // things in result are in either

and then ReadonlySetLike<unknown> as the parameter for the three predicates.

bakkot avatar Feb 01 '24 06:02 bakkot

@fatcerberus It's possible to validate as a function parameter with an approach like the one I mentioned in my original comment which could be leveraged here, but I assume that falls into the category of "too complex for builtin types."

ssalbdivad avatar Feb 01 '24 09:02 ssalbdivad

Hmm, I have mixed feelings about the name ReadonlySetLike. While I get it--it doesn't have any of the mutating methods--it'd be weird (and maybe a bit misleading, for people reading the code) if I actually wanted to make a mutable setlike and had to extend ReadonlySetLike to get there.

Also it doesn't seem completely analogous to ArrayLike since the methods are defined to return a full-fledged Set...

fatcerberus avatar Feb 01 '24 17:02 fatcerberus

but to my knowledge there's no way to allow that without also allowing...

Correct. "This is why we need Comparable<T>!" got brought up multiple times in the discussion 🙂

RyanCavanaugh avatar Feb 01 '24 19:02 RyanCavanaugh

If we are seriously thinking about Comparable<T>, why not take the more conservative option first and then introduce Comparable?

DanielRosenwasser avatar Feb 01 '24 20:02 DanielRosenwasser

The conservative approach still fails basic desirable things like subtyping the output or a.intersect(b) === b.intersect(a)

RyanCavanaugh avatar Feb 01 '24 21:02 RyanCavanaugh

Hmm, I have mixed feelings about the name ReadonlySetLike. While I get it--it doesn't have any of the mutating methods--it'd be weird (and maybe a bit misleading, for people reading the code) if I actually wanted to make a mutable setlike and had to extend ReadonlySetLike to get there.

While I'm fine with both SetLike and ReadonlySetLike, there are other names we could consider as well. In C++, the requirements for a Container are quite similar (i.e., has a size, is iterable), so Container<T> is a possible option if there's a strong negative sentiment towards ReadonlySetLike.

Also it doesn't seem completely analogous to ArrayLike since the methods are defined to return a full-fledged Set...

These methods allocate and return a new Set no matter what object you pass as the argument so long as it has the minimum necessary API for the methods to succeed. In fact, ArrayLike and SetLike are nearly identical in their capabilities, but are tailored to their specific protocols. Both are minimal, read-only representations of their respective full implementations. ArrayLike has a read-only length, the ability to read a single element by index, and the ability to read all elements by index and length. While SetLike has a read-only size, the ability to test whether a single value is in its domain via has(), and the ability to read all elements via keys().

rbuckton avatar Feb 01 '24 21:02 rbuckton

Ah, I think I misinterpreted ReadonlySetLike as being the interface that provided the new methods, which I'm guessing now isn't actually the case - that's the part I was objecting to, since they always return a real Set. My objections may have been misplaced :)

fatcerberus avatar Feb 01 '24 22:02 fatcerberus