-
Notifications
You must be signed in to change notification settings - Fork 62
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
Integration with type systems #9
Comments
Yes, I can also try to do an additional document like the transpiler design doc |
@rbuckton thoughts welcome. Based on feedback re TS and Flow (for example) a good place to record our findings would be: https://github.com/rricard/proposal-const-value-types/blob/master/TypeSystem-Strategy.md Also curious about what we can do with regard to the name conflict with the "Record" type in the TS standard library. |
@rickbutton raised a concern about TS supporting ordering mattering for keys in Records. Not an expert, but here's my stab at discussing this topic:
|
related: #15 |
@mheiber the line above is 404 now, where is the new link I can see the integration of type system?
Now the records is sorted by key, does it no longer be a problem? |
Good point, sounds like it's not a problem! |
Worried about that too. |
😬 |
It'd be tough to change it to match the immutable semantics of records within this proposal. It might be cleanest to introduce Given that tuples defined in this proposal fit the |
The Tuples in this proposal are quite different from TS ReadonlyArrays actually. Tuples have methods that normal arrays don't (pushed etc.) and don't allow other than primitive values inside. Repurposing ReadonlyArrays to mean Tuples would be a major breaking change that I don't think they'll want to make. |
Related microsoft/TypeScript#39831 |
I hope the inferred values from a const a = 'a' // widening type 'a'
const b = {a} // type {a: string}
b.a // type string
const c = #{a} // type record (or something)
c.a // I hope for type 'a', not string! |
There is no need to do literal type widening on immutable bindings. For example, no base primitive type widening happens in the following. let x = { foo: 100 } as const; |
@Aprillion But that (not widening) would be inconvenient. function join<T, Q extends T>(a: T, b: Q) {
if (Array.isArray(a) && Array.isArray(b)) return [...a, ...b]
if (typeof a === 'object') return { ...a, ...b }
return #{...a, ...b}
}
join([1, 2, 3], [4, 5, 6])
join({ a: 1 }, { a: 2, b: 2 })
join(#{ a: 1 }, #{ a: 2 }) If R&T is not widened, TypeScript would report
And that's not good. |
@Jack-Works I don't agree - I want a compiler error in exactly that situation (when I try to modify an immutable property)! Though a TS compiler error on line with The workaround for false positive is really simple |
Ok, const a: { b: 1 } = { b: 1 }
a.b = 1 // This is OK (re-assignment)
const a2: { readonly b: 1 } = { b: 1 }
a.b = 1 // Nope All properties on the records are readonly, but that doesn't mean it should be treated as a non-widened value. (Non-widened value on Let me use another example: let x = #{ a: 1 }
x.a // should be number, not 1 |
In that scenario, the widening should be applied to the mutable variable And IMHO it should produce compile error because |
That's not how TypeScript type inferring works today. When you type Therefore, |
I see your point about backwards compatibility, but please note that TS does not produce runtime safe code (due to various constructs that aren't analyzed by TS), e.g. the following playground: let x = {a: {b: 1}}
x = JSON.parse('{}')
x.a.b Function arguments inferred from a default value might need widening too,, however I am not sure how much the whole immutability feature would be useful without the type narrowing like following: const start = #{type: 'start'}
const success = #{type: 'success', value: 3}
type Action = typeof start | typeof success // non-widened types needed here
const initialState = Record({isLoading: true, value: 0}) // widened types needed here
export default function reducer(state = initialState, action: Action) {
switch(action.type) {
case 'success':
return {isLoading: false, value: action.value} // OK: 3 can be assigned to number
default: {
const _forgottenCase: never = action // error: #{type: 'start'} cannot be assigned to never
return state
}
}
} Currently, some ceremony is needed to make the code work, we have to either use an
Which can be fragile, because a single widening string in the union type (or in 1 element of an array) will make the whole inferred type too wide for any future type narrowing and I was hoping that immutable types will make the immutable scenarios easier, not exactly as complicated as the current immutable-like work with regular objects. |
@Jack-Works perhaps I just mis-understood your original comment and my scenario would work with all the current semantics of type widening - in which case I don't propose any changes to TS type widening, just wanted to make sure I won't have to write |
Would you expect to be able to pass |
Not for that particular const actionCreators = #{
start: () => #{type: 'start'},
success: (value: number) => #{type: 'success', value},
}
type Action = ReturnType<typeof actionCreators[keyof typeof actionCreators]> |
We may need something like |
Working on this. Please watch microsoft/TypeScript#45546 |
Compared to July 2020, the different methods names ( Even still interface Tuple<T extends Primitive> {
at(index: number): T;
map<U extends Primitive>(callbackfn: (value: T, index: number, tuple: Tuple<T>) => U, thisArg?: any): Tuple<U>;
}
interface ReadonlyArray<T> {
at(index: number): T;
map<U>(callbackfn: (value: T, index: number, array: ReadonlyArray<T>) => U, thisArg?: any): Array<U>;
} EDIT: actually |
TS should be fixed to disallow assigning |
Yep, I would imagine a full TS implementation would implement a proper |
I was imagining that Actually taking the example of string template literals, we could have |
Oh right, true! |
And because of how primitives auto-wrap on access TypeScript effectively makes the primitive types subtypes of their object wrappers. So the types
Might also want to have generic primitives too, which would be a new thing (usual convention of generics is to start with a capital letter). i.e. type State = #{
lastUpdatedMs: number;
loggedInUsers: objectPlaceholder<ReadonlySet<ReadonlyUser>>;
messages: #[...string[]];
} I can see generic utility functions wanting to accept both Arrays and Tuples as input to specify |
Yes I considered that too, but I wasn't sure how much of a departure from the existing primitive constraints this would be. I also agree it would be most useful for object placeholder since that primitive doesn't have a syntax notation to use instead. |
The way TypeScript handles this for a TS "tuple" is that we essentially create an intersection type. For example: // the following are approximately the same
type T1 = [number, number];
type T2 = Array<number> & {
"0": number;
"1": number;
length: 2;
}; The key difference is that we preserve the "tupleness" of type T1 = tuple; // primitive base constraint for a JS tuple
type T2 = record; // primitive base constraint for a JS record
type T3 = box Type; // primitive base constraint for a JS box
type T4 = primitive; // possible new intrinsic type containing all primitives, i.e.:
// string | symbol | number | bigint | boolean | record | tuple | box unknown | null | undefined
// Apparent type for a primitive `tuple` (similar to how `String` is the apparent type for a primitive `string`)
interface ReadonlyTuple {
readonly [index: number]: primitive;
readonly length: number;
// ... other prototype methods for a tuple
}
// Apparent type for a primitive `record`
interface ReadonlyRecord {
// ... prototype methods for a record (if any)
}
// Apparent type for a primitive `box`
interface Box {
// ... prototype methods for a box
unbox(): unknown;
}
// Built-in JS tuple type:
type T5 = #[string, number];
// Approximation of JS tuple type using intersection:
type T6 = tuple & {
readonly "0": string;
readonly "1": number;
length: 2;
};
// Built-in JS record type:
type T7 = #{ x: number, y: number };
// Approximation of JS record type using intersection:
type T8 = record & {
readonly x: number;
readonly y: number;
};
// Built-in JS box type:
type T9 = box Date;
// Approximation of a JS box type using intersection:
type T10 = (box unknown) & {
unbox(): Date;
}; |
@rbuckton the interface ObjectPlaceholderConstructor {
<T>(obj: T): placeholder T;
getObject<T>(value: placeholder T): T;
}
var ObjectPlaceholder: ObjectPlaceholderConstructor; It does awfully look like generics though. |
It is generic, but more like There are other things we need to consider as well, such as an approximation of |
Closing this issue in favor of discussing on the typescript repo here: microsoft/TypeScript#49243 |
This proposal sounds like it'll be pretty great for type systems like TypeScript, since it's analogous to ordinary objects. Developers may also want to declare an argument type
@const
, which I think should also work well. We should bring type system maintainers in the loop early and see how this works out, to verify these assumptions.The text was updated successfully, but these errors were encountered: