Implement the JavaScript Array.includes function in the type system. A type takes the two arguments. The output should be a boolean true or false.
在型別系統中實現 JavaScript
的 Array.includes
函數。該型別接受兩個參數,輸出應為 true
或 false
的布林值。
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
接下來,你的任務是讓下面的type cases測試通過:
type cases = [
Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'>, true>>,
Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>, false>>,
Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 7>, true>>,
Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 4>, false>>,
Expect<Equal<Includes<[1, 2, 3], 2>, true>>,
Expect<Equal<Includes<[1, 2, 3], 1>, true>>,
Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,
Expect<Equal<Includes<[boolean, 2, 3, 5, 6, 7], false>, false>>,
Expect<Equal<Includes<[true, 2, 3, 5, 6, 7], boolean>, false>>,
Expect<Equal<Includes<[false, 2, 3, 5, 6, 7], false>, true>>,
Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>,
Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>,
Expect<Equal<Includes<[1], 1 | 2>, false>>,
Expect<Equal<Includes<[1 | 2], 1>, false>>,
Expect<Equal<Includes<[null], undefined>, false>>,
Expect<Equal<Includes<[undefined], null>, false>>,
]
從以下幾個方向來思考:
U
做比較,這可以透過遞迴 (recursion) 的方式來實現。true
,否則繼續遞迴直到陣列耗盡,最後返回 false
。false
,這是遞迴的停止條件。解法:
type Includes<T extends readonly any[], U> = T extends [infer First, ...infer Rest] ?
(Equal<First, U> extends true ? true : Includes<Rest, U> ):
false
(補充這裡使用的 equal)
export type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false
細節分析:
T extends readonly any[]
:這表示 T 是一個只讀的陣列 (readonly array)。extends readonly any[] 用來限制 T
必須是陣列。T extends [infer First, ...infer Rest]
來將陣列的第一個元素 (first element) 和剩下的部分 (remaining elements) 分別命名為 First
和 Rest
。如果 T
是空陣列 (empty array),這個條件不成立,直接返回 false
。Equal<First, U>
比較當前元素 First 與目標 U 是否相等。如果相等,則返回 true
,結束遞迴 (end recursion)。否則,繼續遞迴處理剩餘的陣列 Rest
。T
為空,返回 false
。這樣,我們就能順利通過測試啦 🎉 😭 🎉
空陣列、單元素陣列與多元素陣列的推斷 (Infer) 結果:
type IsEmpty = [] extends any[] ? true : false; // true
type FirstEmpty = [] extends [infer F, ...infer R] ? F : false; // false
type FirstSingle = [1] extends [infer F, ...infer R] ? F : false; // 1
type RestSingle = [1] extends [infer F, ...infer R] ? R : false; // []
type FirstMultiple = [1, 'hi'] extends [infer F, ...infer R] ? F : false; // 1
type RestMultiple = [1, 'hi'] extends [infer F, ...infer R] ? R : false; // ['hi']
// 如果我們的條件改為,想要 T 存在 任意 U 裡面的元素,該如何實作?
type IncludesAny<T extends any[], U extends any[]> = any
// 並讓以下通過呢?:
type cases0 = [
Expect<Equal<IncludesAny<['Kars', 'Esidisi', 'Wamuu', 'Bruno Mars'], ['Black Pink!']>, false>>,
Expect<Equal<IncludesAny<['Kars', 'Esidisi', 'Wamuu', 'Bruno Mars'], ['Black Pink!', 'Bruno Mars']>, true>>
]
解法:
type IncludesAny<T extends readonly any[], U extends readonly any[]> =
U extends [infer First, ...infer Rest] ?
(Includes<T, First> extends true ? true : IncludesArrayAny<T, Rest> ):
false
// 如果我們的條件改為,想要 T 存在 所有 U 裡面的元素,該如何實作?
type IncludesAll<T extends readonly any[], U extends readonly any[]> = any
// 並讓以下通過呢?
type cases1 = [
Expect<Equal<IncludesAll<['Kars', 'Esidisi', 'Wamuu', 'Bruno Mars'], ['Wamuu', 'Bruno Mars']>, true>>,
Expect<Equal<IncludesAll<['Kars', 'Esidisi', 'Wamuu', 'Bruno Mars'], ['Black Pink', 'Bruno Mars']>, false>>,
Expect<Equal<IncludesAll<['Kars', 'Esidisi', 'Wamuu', 'Bruno Mars'], []>, false>>
]
解法:
type IncludesAll<T extends readonly any[], U extends readonly any[]> =
U extends [infer First, ...infer Rest]
? (Includes<T, First> extends true ? (Rest extends [] ? true : IncludesArrayAll<T, Rest>) : false)
: false;
本次介紹了 Includes
的實作,下一關會挑戰 Push
,期待再相見!