型挑戰(zhàn) 00017:用類(lèi)型系統(tǒng)實(shí)現(xiàn)柯里化(Currying)的完整實(shí)戰(zhàn)指南)
示例工程【免費(fèi)下載鏈接】type-challengesCollection of TypeScript type challenges with online judge項(xiàng)目地址https://gitcode.com/GitHub_Trending/ty/type-challenges點(diǎn)擊查看免費(fèi)下載本篇技術(shù)指南圍繞 type-challenges 倉(cāng)庫(kù)中編號(hào) 00017 的困難hard級(jí)挑戰(zhàn)「柯里化 1」展開(kāi)講解如何用 TypeScript 類(lèi)型系統(tǒng)為任意參數(shù)個(gè)數(shù)的函數(shù)推導(dǎo)出柯里化后的類(lèi)型簽名。讀完本文你將掌握變長(zhǎng)元組variadic tuple與遞歸條件類(lèi)型在函數(shù)類(lèi)型變換中的組合用法能夠獨(dú)立完成本挑戰(zhàn)并為理解倉(cāng)庫(kù)中更高難度的「柯里化 2」動(dòng)態(tài)參數(shù)柯里化挑戰(zhàn)打下基礎(chǔ)。挑戰(zhàn)背景柯里化是什么柯里化Currying 是一種將帶有多個(gè)參數(shù)的函數(shù)轉(zhuǎn)換為一系列每個(gè)只帶一個(gè)參數(shù)的函數(shù)的技術(shù)。例如一個(gè)普通的加法函數(shù)const add (a: number, b: number) a b const three add(1, 2)經(jīng)過(guò)柯里化之后它的調(diào)用形態(tài)會(huì)從add(1, 2)變成逐次傳參const curriedAdd Currying(add) const five curriedAdd(2)(3)本挑戰(zhàn)編號(hào) 00017的難點(diǎn)在于傳遞給Currying的函數(shù)參數(shù)個(gè)數(shù)是不確定的你需要在類(lèi)型層面寫(xiě)出一個(gè)通用的變換使得無(wú)論傳入幾個(gè)參數(shù)、什么類(lèi)型都能得到對(duì)應(yīng)的逐層柯里化函數(shù)類(lèi)型。本挑戰(zhàn)標(biāo)注了「困難hard」難度并歸入#array元組/數(shù)組主題分類(lèi)文檔同時(shí)建議在TypeScript 4.0下完成這是因?yàn)楸窘夥ㄒ蕾?lài) TS 4.0 引入的「變長(zhǎng)元組類(lèi)型variadic tuple types」能力[...infer Rest]語(yǔ)法。挑戰(zhàn)目標(biāo)與類(lèi)型規(guī)格原文檔給出了本挑戰(zhàn)的完整規(guī)格需要滿(mǎn)足以下三點(diǎn)柯里化后的函數(shù)每次僅接受一個(gè)參數(shù)參數(shù)逐個(gè)被分配接受完所有參數(shù)后返回最終結(jié)果柯里化函數(shù)每一層的返回值類(lèi)型必須精確推導(dǎo)不能退化成any。也就是說(shuō)對(duì)于(a: string, b: number, c: boolean) true這樣的三參數(shù)函數(shù)期望的類(lèi)型是(a: string) (b: number) (c: boolean) true題目模板與測(cè)試用例解讀模板文件本挑戰(zhàn)的起點(diǎn)在 questions/00017-hard-currying-1/template.ts模板只有一個(gè)空實(shí)現(xiàn)declare function Currying(fn: any): any所有參數(shù)和返回值都被any吞掉了任務(wù)就是把它替換成能正確推導(dǎo)柯里化類(lèi)型的簽名。測(cè)試用例倉(cāng)庫(kù)為每個(gè)挑戰(zhàn)都提供了類(lèi)型級(jí)測(cè)試用例見(jiàn) questions/00017-hard-currying-1/test-cases.ts。它通過(guò)Equal工具類(lèi)型定義于 utils/index.d.ts逐一比對(duì)推導(dǎo)結(jié)果import type { Equal, Expect } from type-challenges/utils const curried1 Currying((a: string, b: number, c: boolean) true) const curried2 Currying((a: string, b: number, c: boolean, d: boolean, e: boolean, f: string, g: boolean) true) const curried3 Currying(() true) type cases [ ExpectEqual typeof curried1, (a: string) (b: number) (c: boolean) true , ExpectEqual typeof curried2, (a: string) (b: number) (c: boolean) (d: boolean) (e: boolean) (f: string) (g: boolean) true , ExpectEqualtypeof curried3, () true, ]從用例中可以提煉出本挑戰(zhàn)必須覆蓋的三個(gè)邊界用例輸入期望輸出覆蓋要點(diǎn)curried13 個(gè)參數(shù)(string, number, boolean)3 層柯里化鏈基本多參數(shù)場(chǎng)景curried27 個(gè)參數(shù)7 層柯里化鏈長(zhǎng)參數(shù)列表與類(lèi)型逐層對(duì)應(yīng)curried30 個(gè)參數(shù)() true() true原樣返回空參數(shù)邊界注意curried3說(shuō)明了一個(gè)重要細(xì)節(jié)當(dāng)函數(shù)沒(méi)有參數(shù)時(shí)柯里化結(jié)果就是它本身不能推導(dǎo)成錯(cuò)誤的形態(tài)。解題思路四步構(gòu)造柯里化類(lèi)型第一步約束入?yún)⒉⑻崛?shù)與返回值首先把Currying的入?yún)⒓s束為函數(shù)類(lèi)型并用infer拆出參數(shù)元組Args與返回值Rdeclare function CurryingT extends (...args: any[]) any(fn: T): CurriedT type CurriedF F extends (...args: infer Args) infer R ? /* 遞歸展開(kāi) Args */ : neverinfer Args得到的不是一個(gè)函數(shù)簽名而是一個(gè)元組例如[string, number, boolean]這正是后續(xù)遞歸的原料。第二步處理空參數(shù)邊界當(dāng)Args是空元組[]時(shí)函數(shù)沒(méi)有參數(shù)可分直接返回一個(gè)接受零參數(shù)并返回R的函數(shù)類(lèi)型type CurriedF F extends (...args: infer Args) infer R ? Args extends [] ? () R : /* 繼續(xù)遞歸 */ : never這一步對(duì)應(yīng)測(cè)試用例中的curried3。第三步逐個(gè)拆出參數(shù)并遞歸生成下一層當(dāng)Args非空時(shí)用變長(zhǎng)元組語(yǔ)法拆出頭元素A與剩余元組RestArgs extends [infer A, ...infer Rest] ? (arg: A) Curried(...args: Rest) R : never這里的關(guān)鍵是拆出第一個(gè)參數(shù)A作為當(dāng)前層函數(shù)的入?yún)⑷缓蟀咽S鄥?shù) 原返回值重新包成一個(gè)函數(shù)(...args: Rest) R繼續(xù)交給Curried遞歸處理。每次遞歸吞掉一個(gè)參數(shù)直到Rest為空觸發(fā)第二步的邊界分支。第四步完整的參考解法將以上三步組合得到一份可提交的解法declare function CurryingT extends (...args: any[]) any(fn: T): CurriedT type CurriedF F extends (...args: infer Args) infer R ? Args extends [] ? () R : Args extends [infer A, ...infer Rest] ? (arg: A) Curried(...args: Rest) R : never : never用測(cè)試用例驗(yàn)證推導(dǎo)過(guò)程對(duì)(a: string, b: number, c: boolean) true第一輪拆出string得到(arg: string) Curried(b: number, c: boolean) true第二輪拆出number第三輪拆出boolean后Rest為空落入() R分支返回true最終展開(kāi)為(a: string) (b: number) (c: boolean) true與curried1的期望完全一致對(duì)curried2的 7 參數(shù)函數(shù)遞歸會(huì)連續(xù)展開(kāi) 7 層類(lèi)型推導(dǎo)逐一對(duì)應(yīng)對(duì)curried3的空參數(shù)函數(shù)Args extends []直接命中返回() true。知識(shí)點(diǎn)剖析這道題練的是什么變長(zhǎng)元組類(lèi)型Variadic Tuple Types[infer A, ...infer Rest]是 TS 4.0 的核心新語(yǔ)法它允許在元組中間/末尾展開(kāi)剩余元素。沒(méi)有它我們只能寫(xiě)[infer A, ...]這類(lèi)無(wú)法捕獲剩余部分的模式。這正是原文檔建議「在 TypeScript 4.0 下完成」的原因——若使用更早版本...infer Rest無(wú)法編譯通過(guò)。條件類(lèi)型 infer 的遞歸Curried是典型的遞歸條件類(lèi)型它以F extends (...args: infer Args) infer R為入口在真分支內(nèi)繼續(xù)構(gòu)造新的函數(shù)類(lèi)型并調(diào)用自身。遞歸的終止條件是Args extends []與運(yùn)行時(shí)柯里化的「參數(shù)耗盡即返回結(jié)果」語(yǔ)義一一對(duì)應(yīng)。與倉(cāng)庫(kù)相關(guān)挑戰(zhàn)的關(guān)聯(lián)倉(cāng)庫(kù)文檔在本挑戰(zhàn)底部列出了三條相關(guān)挑戰(zhàn)可以作為練習(xí)鏈路14?第一個(gè)元素用infer A從元組中提取首元素是本挑戰(zhàn)[infer A, ...infer Rest]拆解的前置熱身16?排除最后一項(xiàng)對(duì)元組做「去尾」操作練習(xí)元組的拆解與重組462?柯里化 2地獄級(jí)進(jìn)階版要求柯里化函數(shù)每次可接受至少一個(gè)參數(shù)、數(shù)量不限對(duì)應(yīng)bind這類(lèi)動(dòng)態(tài)傳參場(chǎng)景在理解本挑戰(zhàn)「每次一個(gè)參數(shù)」的遞歸骨架后可進(jìn)一步挑戰(zhàn)該題。驗(yàn)證與運(yùn)行方式本倉(cāng)庫(kù)是類(lèi)型挑戰(zhàn)集合所有用例通過(guò)Equal進(jìn)行編譯期斷言。完成template.ts的填寫(xiě)后可在本地運(yùn)行 TypeScript 編譯器倉(cāng)庫(kù)根目錄 package.json 的 devDependencies 中配置了typescript ^5.3.3以tsc檢查 questions/00017-hard-currying-1/test-cases.ts 是否存在類(lèi)型錯(cuò)誤npx tsc --noEmit questions/00017-hard-currying-1/test-cases.ts若Equal斷言通過(guò)說(shuō)明你的Curried類(lèi)型推導(dǎo)與期望完全一致Equal的實(shí)現(xiàn)基于函數(shù)參數(shù)逆變比較比簡(jiǎn)單的extends互檢更嚴(yán)格見(jiàn) utils/index.d.ts。小結(jié)本挑戰(zhàn)的核心價(jià)值在于訓(xùn)練「把運(yùn)行時(shí)行為映射為遞歸類(lèi)型變換」的思維柯里化把f(a, b, c)變成f(a)(b)(c)類(lèi)型層面就對(duì)應(yīng)把參數(shù)元組[A, B, C]遞歸展開(kāi)為(arg: A) (arg: B) (arg: C) R。掌握了「infer 拆元組 遞歸終止分支 變長(zhǎng)元組重組」這套組合拳你就擁有了應(yīng)對(duì)絕大多數(shù)函數(shù)簽名變換類(lèi)挑戰(zhàn)包括后續(xù)的柯里化 2的核心能力。贊分享示例工程【免費(fèi)下載鏈接】type-challengesCollection of TypeScript type challenges with online judge項(xiàng)目地址https://gitcode.com/GitHub_Trending/ty/type-challenges點(diǎn)擊查看免費(fèi)下載相關(guān)推薦TypeScript 柯里化類(lèi)型挑戰(zhàn) Currying 100017-hard從零推導(dǎo)函數(shù)柯里化的完整類(lèi)型簽名TypeScript 柯里化類(lèi)型挑戰(zhàn) Currying 100017 hard從零推導(dǎo)函數(shù)柯里化的完整類(lèi)型簽名 本篇文章圍繞 type challenge示例工程TypeScript 柯里化實(shí)戰(zhàn)用箭頭函數(shù)鏈實(shí)現(xiàn)部分應(yīng)用CurryingTypeScript 柯里化實(shí)戰(zhàn)用箭頭函數(shù)鏈實(shí)現(xiàn)部分應(yīng)用Currying 柯里化Currying是將一個(gè)多參數(shù)函數(shù)拆解為一系列單參數(shù)函數(shù)的函數(shù)式編程技教程type-challenges 題解Hard 難度 Currying 1柯里化 1類(lèi)型體操完整解析type challenges 題解Hard 難度 Currying 1柯里化 1類(lèi)型體操完整解析 導(dǎo)讀 Currying 1編號(hào) 17是 type示例工程上一篇開(kāi)源項(xiàng)目Anchor CMS快速指南與常見(jiàn)問(wèn)題解答下一篇Webgrind 開(kāi)源項(xiàng)目常見(jiàn)問(wèn)題解決方案創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考