#Function that returns a tuple type from array of keys

62 messages · Page 1 of 1 (latest)

prisma breach
#

I'm trying to construction a function where you pass an object, and an array of keys (This is a simplified example to illustrate what im trying to achieve). Then return is an array that contain those keys, and has the type of a tuple. The problem that I'm struggling with is that the return type is the union of all types in the type, not the actual value of the given key.

I guess the error lies in the K parameter not infering the key granular enough..? Is this even solveable?

type User = {
    name: string,
    location: string
    age: number    
}

const fooUser: User = {
    age: 30,
    name: "Foo",
    location: "Bar",
}

const getSelected = <T extends User, K extends (keyof User)[]>(user: T, keys: K) => {
    return keys.map((k) => user[k])
}

// this errors but gives correct types
type InferKeys <T extends (keyof User)[] > = T extends [infer First, ...infer Rest] ? First extends keyof User ? [User[First], ...InferKeys<Rest>] : [User[First]] : []
type MyInferedKeys = InferKeys<['age', 'name']>
           //  ^? type MyInferedKeys = [number, string]
        


const testArray = getSelected(fooUser, ['age', 'name']) 
   //  ^? const testArray : (string | number)[]
    // Would want [string, number]
chilly shoal
#

the return is an array that contains those keys? do you mean the properties?

#

would the proper implementation of getSelected be return keys.map((k) => user[k])?

#

you could do something like this, but tuple.map doesn't actually map the tuple, unfortunately
you might have to cast that, not sure if there's a cleaner way

fringe canyonBOT
#
that_guy977#0

Preview:```ts
type User = {
name: string,
location: string
age: number
}

const fooUser: User = {
age: 30,
name: "Foo",
location: "Bar",
}

const getSelected = <T extends User, const K extends readonly (keyof T)[]>(user: T, keys: K): { [P in keyof K]: T[K[P]] } => {
...```

prisma breach
#

Would you mind giving an example of how to cast this? as the compiler is screaming at the return, and I'm not sure how to cast to the mapped type?

chilly shoal
#

well, just, casting the entire thing

prisma breach
#

You mean like ```ts
const testArray = getSelected(fooUser, ['age', 'name']) as [number, string]

chilly shoal
#

something like this, perhaps

fringe canyonBOT
#
that_guy977#0

Preview:```ts
type User = {
name: string,
location: string
age: number
}

const fooUser: User = {
age: 30,
name: "Foo",
location: "Bar",
}

type MapProps<T, K extends readonly (keyof T)[]> = { [P in keyof K]: T[K[P]] } & T[keyof T][];

const getSelected = <T extends User, const K extends readonly (keyof T)[]>(user: T, keys: K): MapProps<T, K> => {
...```

prisma breach
#

MVP!

#

Gonna take a while for me to actually grasp this, but gonna dig down into it to understand how it works! Thanks so much, might pm you later about it.

#

!resolved

prisma breach
#

So you basically solved it by indexing into the type to extract the things that we actually want:

{[P in keyof K]: T[K[P]]}

And then to make the compiler happy you had to create an intersection that included all the possible keys from the type: ```ts
T[keyof T]

this was made to satisfy the `key` in: ```ts
keys.map((k) => obj[k]) as MapProps<T, K>;

is this correct?

chilly shoal
#

the first part is a mapped type:

#

!hb mapped type

fringe canyonBOT
prisma breach
#

Didn't know you could use mapped types over arrays, thought they were only used for types to construct new types, such as remaping or filtering.

chilly shoal
#

arrays are types too

#

this is remapping

jade cove
#
['a', 'b', 'c']
```is```ts
{
  '0': 'a',
  '1': 'b',
  '2': 'c'
}

With some extra methods like .map() etc

#

When you do array[0], the number gets converted to a string, so it's really array['1'], which is the same way you'd access the '1' property on an object.

#

TypeScript does some fanangaling to help with the number->string conversion of keys, so that we can use numbers to index arrays, but internally they are converted to strings.

#

Although TS is TS, but yeah I guess it wasn't bit stretch for them to apply mapping to arrays.

prisma breach
# jade cove Well arrays are just objects

Ahh, Thanks for the explanation Dude!
I guess that explains why:

type MappedDerivedAtoms2<T, K extends readonly (keyof T)[]> = {
  [P in keyof K]: P;
}
type MyMappedAtoms = MappedDerivedAtoms2<User, ['age', 'location']>

makes MyMappedAtoms equal to ['0', '1'].
and also that's why we can use [number] to get an union of the types, right?
Btw if the [number] indexing is correct, do you know if that's some kind of internal implementation details specific to using [number] to get distributivty? kinda like T extends T

jade cove
#

Yeah makes sense that you get ['0', '1'] there.
number is considered an indexer of array, like string would be an indexer of Record<string, unknown>, so that just grabs a union of the values for you. I guess string[] is very similar to { [key: number]: string } if that syntax is allowed. But you can also think of it as accessing the values of the array with array[number] (the type returned would be one of the values, i.e. a union of the values) which works whether it is object based or not.

#

I don't think indexing by number or anything else relates to distributivity, I think we can only use extends to do that.

#

I don't totally understand the question there though.

prisma breach
#

I think my question was that using [number] to index, do you know if that is shorthand for creating disrubitivty that's created by the compiler, like take these for example:

type MyArray = ['a','b','c']
type Distribute<T extends any[]> = T extends [infer First, ...infer Last] ? Last extends any['length']? First | Distribute<Last> : First : never
type OneWay = MyArray[number]
type SecondWay = Distribute<MyArray>

Both OneWay and SecondWay returns a union of 'a' | 'b' | 'c', one with explicit distrubtivity and one which uses [number] and magically works.

jade cove
#

Well not really

#

if you do

#
const tuple = ['a', 'b', 'c'] as const
const value = tuple[ Math.floor(Math.random() * 3) ]

Then what value is value going to have, and what type should it have?

#

Math.floor(Math.random() * 3) is randomly 0, 1 or 2 by the way

chilly shoal
#

!hb indexed access

fringe canyonBOT
chilly shoal
#

btw, that extends any['length'] seems like it'd just be any...

jade cove
#

meant to be { length: number } ?

chilly shoal
#

type Distribute<T extends readonly unknown[]> = T extends [infer First, ...infer Tail extends readonly unknown[]] ? First | Distribute<Tail> : never

prisma breach
#

Ohh damn that's some 🔥 , never knew that about using math.random() to index an array, that it would give a type that's equal to [number], Makes total sense though. Thanks for explaining that

#

And yeh ur right about that any['length'], didn't know u could extend the Infered type in that way, i Thought that was just applicable for parsing template strings, or numbers such as:

type MyValue<T extends string> = T extends `${infer Digit extends number}` ? Digit : never  
type Value = MyValue<'42'>

Learning tons today, thx guys!

jade cove
# prisma breach Ohh damn that's some 🔥 , never knew that about using math.random() to index an ...

The point I was trying to make : ) was that if you access that array/tuple with a number of 0, 1 or 2, which is essentially what TS is doing when you index it with [number], you would get either 'a', 'b', or 'c', And the way we represent this type is 'a' | 'b' | 'c'. So all that TS is doing when you write MyArray[number] is telling you the type that describes the values you can access in that way.

If you do MyArray['map'] you'll get the type of the map method that exists on the Array class.
If you do MyArray['map' | 'sort' | 'forEach'] now you are accessing multiple methods. This is similar to MyArray[number] because number is effectively an infinite union of all of the individial numbers, then you now get a union of the types of the 3 methods.

fringe canyonBOT
#
sandiford#0

Preview:ts type MyArray = string[] type ValueOfMyArray = MyArray[number] // string type MyTuple = ["a", "b"] type ValueOfMyTuple = MyTuple[number] // "a" | "b" type MyArrayMap = MyTuple["map" | "sort" | "forEach"] // (<U>(callbackfn: (value: "a" | "b", index: number, array: ("a" | "b")[]) => U, thisArg?: any) => U[]) // | ((compareFn?: ((a: "a" | "b", b: "a" | "b") => number) | undefined) => MyTuple) // | ((callbackfn: (value: "a" | "b", index: number, array: ("a" | "b")[]) => void, thisArg?: any) => void)

prisma breach
# jade cove The point I was trying to make : ) was that if you access that array/tuple with ...

Ahhh I think i got it, So it basically allows us to get all the types that exist in the in the array by being a way to express the infinite possibilies(but capped at the length-1) of the array. And that's why, passing a union of number values works in the same way, so we can pass '0' | '1' to the same array, and get back 'a' | 'b'

type MyTuple = ['a', 'b', 'c']
type ValueOfMyTuple = MyTuple['0' | '1'] // "a" | "b"

Btw I'm kinda new to this discord, where is the best place to have these kind of discussions, in the individual threads or is these some other channel more suited?

jade cove
#

Technically MyArray[number] should be something like 'a' | 'b' | 'c' | undefined, but I think for convenience it skips the undefined

#

No harm in chatting in your own help thread

#

Having these chats in someone else'e help thread might not be so good

#

In some places discord lets you create a new thread off from a message, so it would pop up on the side. Doesn't seem to work in this thread though.

chilly shoal
#

yeah you can't nest threads

jade cove
chilly shoal
#

see also T[keyof T]

fringe canyonBOT
#
sandiford#0

Preview:ts const foo = ["foo", "bar"] console.log("forEach") foo.forEach((value: string, key: number) => { console.log("key is", typeof key, key) }) console.log("for") for (const key in foo) { console.log("key is", typeof key, key) } console.log("for values") ...

jade cove
#

You can see some of the number <-> string weirdness of arrays in this example

#

Can be a bit confusing sometimes working with array keys

chilly shoal
#

you should just treat "array keys" and "indices" separately
"array keys" are just object/property keys as strings, indices are keys in the context of arrays and they're transformed into numbers

jade cove
#

related, although they are objects not arrays

export type Map = Record<number, MapCol>
export type MapCol = Record<number, Tile>
function assignSegmentToMap(segment: Segment) {
    for (const x in segment.tiles) {
        const col = segment.tiles[x]
        const mapCol = map[x] ?? []
        for (const y in col) {
            mapCol[y as unknown as number] = col[y as unknown as number]!
        }
    }
}
#

Apparently just using Record<string, X> works better

fringe canyonBOT
#
sandiford#0

Preview:ts type _Map = Record<string, null> const map: _Map = {} map[1] = null // fine map[2] = null // fine