Intention
I'm trying to add immutability-by-default everywhere I can in my code.
I use generics because I don't want to:
- write twice every getters (that's the critical reason, otherwise a
DeepWritable
type would do) - write twice every data-types
- write more stuff to declare an immutable parameter than a mutable one, that's what I mean by immutable-by-default.
Current situation
I've got a deep-readonly type:
// Simplified: Only handle types used in this example
type DeepReadonly<T> = T extends number
? T
: T extends ReadonlyArray<infer V>
? ReadonlyArray<DeepReadonly<V>>
: { readonly [K in keyof T]: DeepReadonly<T[K]> };
And my data types can be mutable or immutable with a type parameter:
type Mutability = 'const' | 'mut';
interface PlayerMut {
life: number;
}
type Player<M extends Mutability = 'const'> = M extends 'const' ? DeepReadonly<PlayerMut> : PlayerMut;
interface GameStateMut {
players: Player[];
}
type GameState<M extends Mutability = 'const'> = M extends 'const' ? DeepReadonly<GameStateMut> : GameStateMut;
I can use GameState
as immutable (thus, by default) and GameState<'mut'>
as mutable.
Also, GameState<'mut'> extends GameState
is valid. So I can pass mutable states to a function expecting an immutable one (.i.e it won't modify it), but not the contrary (.i.e it would try to modify it).
So far so good...
Problem
Then, I am trying to write a getter function which would return an immutable part of the state by default, but could return a mutable one if we wish (and providing a mutable GameState
).
This fails:
function getFirstPlayer<M extends Mutability = 'const'>(gameState: GameState<M>): Player<M> {
// error: Type 'Readonly<PlayerMut>' is not assignable to type 'Player<M>'
return gameState.players[0]; // dirty workaround: ...as Player<M>
}
In my head, this should work. But TypeScript doesn't understand it (gameState
type in the function is equivalent to GameState<'mut'> | GameState<'const'>
, always).
Question
Is there a way to make the above work without resorting to as
?
as
works but feels like cheating. It is putting a hole in the type checking,
which is making the whole program less future-proof.