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