I'm trying to write a deep recursive Mutable
type:
Mutable<T> = ...
// remove all "readonly" modifiers
// convert all ReadonlyArray to Array
// etc.
// and do it all recursively
const mutate = <T>(val: T, mutateFn: (mutableVal: Mutable<T>) => void): T => {
return null as any
}
It's working fine as long as I don't use generics:
// works fine
mutate([] as ReadonlyArray<string>, mutableVal => {
mutableVal.splice(42, 0, "test");
});
But when using it within a generic function I get an error:
// Error: Argument of type 'T' is not assignable to parameter of type 'Mutable<T>'.
const doSomething = <T>(array: ReadonlyArray<T>, index: number, elem: T) => {
mutate(array, mutableVal => {
// Here's the error
// v
mutableVal.splice(index, 0, elem);
});
}
I understand, that the mutable array's type is Array<Mutable<T>>
, and that splice
now expects a Mutable<T>
value, instead of T
. But I can't figure out how to solve it.
Do you have any idea how to solve this?
I've created a TypeScript Playground, so you can play with the code: Link to TypeScript Playground
My suggestion is to do something like this:
const doSomething = <T>(array: ReadonlyArray<T>, index: number, elem: T) => {
mutate({ array: array, elem: elem }, mutableVal => {
mutableVal.array.splice(index, 0, mutableVal.elem);
});
}
The idea is that you need elem
to be mutable in order to add it to a deep-mutable array, but your original call was not doing that. Since you want to mutate both array
and possibly elem
, the most straightforward solution is to pass in an object containing both array
and elem
, and operate on the deep-mutable version of that.
Only you know if it's acceptable to call mutate()
on elem
as well as on array
, since the implementation of mutate()
is left out. I'm guessing it's going to be something involving an assertion like this:
const mutate = <T>(val: T, mutateFn: (mutableVal: Mutable<T>) => void): T => {
mutateFn(val as Mutable<T>); //🤷♀️
return val;
}
in which case I'd say "who cares" whether you call mutate()
on elem
, or whether you just assert elem
to its mutable counterpart inside doSomething()
. On the other hand, if your implementation is something fancier involving cloning, then you should think about whether it makes sense to call it on elem
or not.
Okay, hope that helps. Good luck!