#I have a union of ReadonlyArrays. How can I ergonomically make a shallow copy of an instance?

21 messages · Page 1 of 1 (latest)

remote knoll
#

Below is a minimal representation of my problem. I have a type that's a union of ReadonlyArrays. Normally when I want a shallow copy I can do something like Array.from(array) or [...array] to get a shallow copy. It doesn't work for these unions, even though logically it should be sound.

Is there an easy/ergonomic way to get the type system happy w/o sacrificing performance?

function shallowCopy1(value: ReadonlyArray<string> | ReadonlyArray<number>): string[] | number[] {
    return [...value]
}

function shallowCopy2(value: ReadonlyArray<string> | ReadonlyArray<number>): string[] | number[] {
    return Array.from(value)
}

https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABAZwBYEMA2m4HcDCcADgJ4CMAFAG5YgCmAXIgEp3oAmCmJAggE590JADzIofGGADmAPkQAfFm05hu-QSLAgAtgCM6fGQEomYidIDaAXQWIteg9cQBvAFCIPiPnSgg+SCwA6YJpMeitXAF9XV1BIWAQUDGw8QlIAJmpaRiUOLl4BIVFxSVlbVjzVAo1he31DExQSyxtFOscbN08vHz8kdSFA4D44bSywuiMomKA

steady oreBOT
#

@remote knoll Here's a shortened URL of your playground link! You can remove the full link from your message.

akajb#0

Preview:```ts
function shallowCopy1(
value: ReadonlyArray<string> | ReadonlyArray<number>
): string[] | number[] {
return [...value]
}

function shallowCopy2(
value: ReadonlyArray<string> | ReadonlyArray<number>
): string[] | number[] {
return Array.from(value)
}```

shrewd yoke
#

i'd make the function generic:

steady oreBOT
#
mkantor#0

Preview:```ts
function shallowCopy<
A extends
| ReadonlyArray<string>
| ReadonlyArray<number>

(value: A): A[number][] {
return [...value]
}```

shrewd yoke
#

as a bonus you're now preserving whether you had numbers or strings in the input

#

in fact if this is all your function does you can generalize it to work with any array:

steady oreBOT
#
mkantor#0

Preview:ts function shallowCopy<A extends ReadonlyArray<unknown>>( value: A ): A[number][] { return [...value] }

remote knoll
#

What's that syntax? A[number][]? Is it saying some collection that's number indexable or something?

shrewd yoke
#

it's called "indexed access". it's the same idea as this:

steady oreBOT
#
type A = { a: 'hello' }
type B = A['a']
//   ^? - type B = "hello"
shrewd yoke
#

but is using number to index into an array type, which is looking up the value type of the array (since when you do arrray[someNumber] at runtime that's what you get back)

#

!hb indexed access

steady oreBOT
remote knoll
#

I should've mentioned my ideal would be something inline. The functions were for demonstration purposes, but that muddied the waters.

shrewd yoke
#

can you share the context?

remote knoll
#

I'm the middle man between two contracts, and I need to translate one to the other.

The first one has an attribute: ReadonlyArray<string> | ReadonlyArray<number>.
The second one has an attribute: string[] | number[]

In other instances I was able to do Array.from(readonlyArray) to get a shallow copy. This does not work when it's a union of arrays as Array.from's generic parameter is on the element of the array, which means it can't be used.

Alternatively I tried [...array], but that assumes the type as (string | number)[], which is a completely different type than the original.

shrewd yoke
#

got some example code you can share? it's going to come down to the specifics of how things are typed and if/where inference occurs

remote knoll
#

Apologies for disappearing. I've tried to recreate a minimal example:

steady oreBOT
#
akajb#0

Preview:```ts
interface OriginContract {
nonUnionField: ReadonlyArray<string>
unionField:
| ReadonlyArray<string>
| ReadonlyArray<number>
}

interface DestinationContract {
nonUnionField: string[]
unionField: string[] | number[]
}

function translateContract(
originContract: OriginContract
): DestinationContract
...```

shrewd yoke
#

i see, that's a bit trickier. i suspect you'll need to use an assertion like this:

steady oreBOT
#
mkantor#0

Preview:```ts
...
type DistributedArray<T> = T extends T ? T[] : never // distribute over the union T
function cloneArray<T extends readonly unknown[]>(
source: T
): DistributedArray<T[number]> {
return Array.from(source) as DistributedArray<
T[number]

}

function translateContract(
originContract: OriginContract
): DestinationContract {
return {
nonUnionField: cloneArray(
originContract.nonUnionField
),
unionField: cloneArray(originContract.unionField),
}
}
...```