的函數(shù)式狀態(tài))
fp-ts Store 模塊完全指南用 Comonad 理解帶焦點(diǎn)的函數(shù)式狀態(tài)【免費(fèi)下載鏈接】fp-tsFunctional programming in TypeScript項(xiàng)目地址: https://gitcode.com/gh_mirrors/fp/fp-ts導(dǎo)讀Store是 fp-ts 中基于Comonad余單子思想實(shí)現(xiàn)的經(jīng)典數(shù)據(jù)結(jié)構(gòu)它將「一個(gè)可讀取當(dāng)前狀態(tài)的函數(shù)peek」與「一個(gè)當(dāng)前焦點(diǎn)位置pos」打包在一起從而在純函數(shù)式代碼中表達(dá)帶焦點(diǎn)的上下文。本文以 docs/modules/Store.ts.md 為主線結(jié)合 src/Store.ts 的源碼實(shí)現(xiàn)與 test/Store.ts 的測(cè)試用例系統(tǒng)講解Store的模型、全部實(shí)用函數(shù)seek/seeks/peeks/experiment等、Comonad 實(shí)例以及它與Comonad、Extend、Functor等抽象之間的關(guān)系。讀完本文你將能夠熟練創(chuàng)建Store、使用管道式 API 完成位置移動(dòng)與局部取值并理解余單子編程的基本模式。一、Store 是什么模型與核心直覺1.1 Store 接口定義StoreS, A在 fp-ts 中定義于 src/Store.ts類型參數(shù)含義如下Sposition位置類型即狀態(tài)空間的坐標(biāo)類型Avalue取值類型即從位置讀取到的值類型。export interface StoreS, A { readonly peek: (s: S) A readonly pos: S }接口只有兩個(gè)字段字段類型含義peek(s: S) A從任意位置s讀取對(duì)應(yīng)的值只讀查詢函數(shù)posS當(dāng)前焦點(diǎn)位置當(dāng)前光標(biāo)所在的坐標(biāo)Store的核心直覺是它像一枚鏡頭或游標(biāo)——peek描述了整個(gè)世界從任意坐標(biāo)到值的映射pos標(biāo)記了你當(dāng)前注視著的位置。這使它天然適合表達(dá)光標(biāo)文本編輯器位置 緩沖區(qū)讀取、棋盤/網(wǎng)格坐標(biāo) 格子內(nèi)容查詢等帶焦點(diǎn)的場(chǎng)景。1.2 創(chuàng)建一個(gè) Store由于Store只是一個(gè)普通接口直接構(gòu)造對(duì)象字面量即可。測(cè)試用例 test/Store.ts 展示了典型構(gòu)造方式——用S.size來自 src/string.ts 的size: (s: string) number作為peek即讀取任意字符串的長(zhǎng)度import * as _ from fp-ts/Store import * as S from fp-ts/string // peek 表示從任意字符串位置讀到其長(zhǎng)度pos 表示當(dāng)前焦點(diǎn)在 a 處 const wa: _.Storestring, number { peek: S.size, pos: a }此時(shí)extract(wa)會(huì)執(zhí)行wa.peek(wa.pos)即讀取a的長(zhǎng)度得到1。1.3 取回當(dāng)前值extractextract是 Comonad 的對(duì)偶于 Monad 的return的關(guān)鍵操作簽名見文檔與源碼 src/Store.tsexport declare const extract: E, A(wa: StoreE, A) A實(shí)現(xiàn)極其簡(jiǎn)單(wa) wa.peek(wa.pos)——對(duì)當(dāng)前位置執(zhí)行 peek取出焦點(diǎn)處的值。它不改變 Store 本身是純讀取。二、位置操作三件套seek / seeks / peeks這三者是Store使用頻率最高的工具函數(shù)文檔中分別標(biāo)注為Reposition the focus...與Extract a value from a position which depends on the current position。2.1 seek把焦點(diǎn)移動(dòng)到指定位置export declare function seekS(s: S): A(wa: StoreS, A) StoreS, A源碼實(shí)現(xiàn)src/Store.tsreturn (wa) ({ peek: wa.peek, pos: s })seek保留原有peek僅把pos替換為目標(biāo)位置s。例如測(cè)試 test/Store.ts 中焦點(diǎn)從a移到aa后extract結(jié)果為2aa的長(zhǎng)度。2.2 seeks根據(jù)當(dāng)前位置計(jì)算新焦點(diǎn)export declare function seeksS(f: EndomorphismS): A(wa: StoreS, A) StoreS, A實(shí)現(xiàn)src/Store.tsreturn (wa) ({ peek: wa.peek, pos: f(wa.pos) })其中f: EndomorphismS即(s: S) S的自同態(tài)函數(shù)定義見 src/Endomorphism.ts。seeks先對(duì)當(dāng)前焦點(diǎn)施加f得到新位置再更新pos。測(cè)試用例中seeks((s) s a)把a(bǔ)變?yōu)閍aextract得到2test/Store.ts。seekvsseeks的區(qū)分seek接收具體的坐標(biāo)值seeks接收坐標(biāo)變換函數(shù)。后者常用于相對(duì)移動(dòng)例如光標(biāo)右移一位可寫為seeks((x) x 1)。2.3 peeks在不改變焦點(diǎn)的情況下窺探其他位置export declare function peeksS(f: EndomorphismS): A(wa: StoreS, A) A實(shí)現(xiàn)src/Store.tsreturn (wa) wa.peek(f(wa.pos))與seeks不同peeks不改變pos而是基于當(dāng)前焦點(diǎn)計(jì)算出目標(biāo)位置后直接peek取值返回A而非新的Store。它適合偷看相鄰位置的值而不移動(dòng)光標(biāo)。測(cè)試中peeks((s) s a)返回2test/Store.ts且原 Store 的焦點(diǎn)保持不變。三、批量探測(cè)experimentexperiment是peeks的批量版本文檔描述為Extract a collection of values from positions which depend on the current position。3.1 簽名與多重重載experiment針對(duì)不同 Kind 結(jié)構(gòu)提供了 5 個(gè)重載文檔與 src/Store.ts 完全一致核心邏輯只有一行return (f) (wa) F.map(f(wa.pos), (s) wa.peek(s))它接收兩個(gè)參數(shù)并柯里化一個(gè)Functor實(shí)例F支持Functor3/Functor3C/Functor2/Functor2C/Functor1/FunctorHKT各檔一個(gè)函數(shù)f: (s: S) FS它根據(jù)當(dāng)前焦點(diǎn)位置產(chǎn)生一批候選位置包在某個(gè)函子容器中如數(shù)組、Option、ReadonlyArray等。然后對(duì)每個(gè)候選位置執(zhí)行wa.peek最終得到FA。3.2 實(shí)戰(zhàn)示例測(cè)試 test/Store.ts 展示了與ReadonlyArray函子配合的用法pipe( wa, // Storestring, numberpeek S.sizepos a _.experiment(RA.Functor)((s) [s, s a]) // 候選位置[a, aa] ) // 結(jié)果[1, 2]即 [size(a), size(aa)]也可以換用Option、IO等任意 Functorexperiment會(huì)自動(dòng)按對(duì)應(yīng)容器聚合結(jié)果。這是以當(dāng)前焦點(diǎn)為中心批量探測(cè)鄰域的通用工具。四、Comonad 視角extend 與 duplicateStore是 fp-ts 中余單子的代表性實(shí)例理解extend/duplicate是掌握它的關(guān)鍵。4.1 extend把整片上下文的計(jì)算推廣到每個(gè)位置export declare const extend: E, A, B(f: (wa: StoreE, A) B) (wa: StoreE, A) StoreE, B實(shí)現(xiàn)src/Store.tsexport const extend (f) (wa) ({ peek: (s) f({ peek: wa.peek, pos: s }), pos: wa.pos })extend接收一個(gè)消費(fèi)整個(gè) Store 得出一個(gè)值的函數(shù)f: (wa: StoreE, A) B返回新的 Store新 Store 的peek會(huì)在任意位置s處臨時(shí)把焦點(diǎn)設(shè)為s再調(diào)用f計(jì)算該位置上的局部結(jié)果pos保持原焦點(diǎn)不變。這正是對(duì)偶于bindchain的上下文傳播操作——它是后面會(huì)講到的細(xì)胞自動(dòng)機(jī)、平滑濾波等每個(gè)點(diǎn)由鄰域決定算法的基石。測(cè)試中的用法test/Store.tspipe( wa, _.extend((wa) _.extract( pipe( wa, _.map((n) n 1) ) ) ) )4.2 duplicate制造帶游標(biāo)的光標(biāo)export declare const duplicate: E, A(wa: StoreE, A) StoreE, StoreE, A源碼實(shí)現(xiàn)極其簡(jiǎn)潔src/Store.tsexport const duplicate /*#__PURE__*/ extend(identity)duplicate就是extend(identity)——把內(nèi)部每個(gè)位置上臨時(shí)焦點(diǎn)化的 Store作為值得到一個(gè)StoreE, StoreE, A。測(cè)試驗(yàn)證了extract(extract(duplicate(wa))) 1test/Store.ts這恰好印證了余單子定律extract ° duplicate id在該實(shí)例上的成立。三者關(guān)系extend(f) map(f) ° duplicate這是 Extend 公理的一部分duplicate因此可以被視為extend的通用化版本。五、映射操作map 與 flap5.1 map把函數(shù)提升進(jìn) Store 上下文map是標(biāo)準(zhǔn) Functor 操作文檔說明mapcan be used to turn functions(a: A) Binto functions(fa: FA) FB。簽名export declare const map: A, B(f: (a: A) B) E(fa: StoreE, A) StoreE, B實(shí)現(xiàn)src/Store.ts保持pos不變把f復(fù)合進(jìn)peekexport const map (f) (fa) ({ peek: (s) f(fa.peek(s)), pos: fa.pos })5.2 flap把容器內(nèi)的函數(shù)應(yīng)用到固定值上export declare const flap: A(a: A) E, B(fab: StoreE, (a: A) B) StoreE, Bflapv2.10.0 加入把值a: A應(yīng)用到容器內(nèi)承載的函數(shù)上。fp-ts 中flap的通用實(shí)現(xiàn)定義于 src/Functor.tsreturn (a) (fab) F.map(fab, (f) f(a))而Store.flap是通過flap_(Functor)從Functor實(shí)例派生的src/Store.ts/*#__PURE__*/標(biāo)記使其可被打包器做常量折疊。當(dāng)你手里有一個(gè)StoreE, (a: A) B并想把它應(yīng)用到固定參數(shù)a上時(shí)flap比手寫map((f) f(a))更直觀。六、類型體操URI、類型 lambda 與實(shí)例6.1 URI 與類型 lambda 注冊(cè)Store是二元類型構(gòu)造子兩個(gè)類型參數(shù)S、A通過URI常量接入 HKT 系統(tǒng)src/Store.tsexport const URI Store export type URI typeof URI declare module ./HKT { interface URItoKind2E, A { readonly [URI]: StoreE, A } }URI常量運(yùn)行時(shí)標(biāo)識(shí)值為字符串StoreURI類型別名把字符串字面量類型Store暴露給類型層URItoKind2的declare module增強(qiáng)告訴 fp-ts 的 HKT 查找表Store對(duì)應(yīng)StoreE, A這是experiment等泛型函數(shù)能對(duì)Store做類型推斷的前提。6.2 Functor 與 Comonad 實(shí)例自 v2.7.0 起fp-ts 提供小而具體的實(shí)例src/Store.tsexport const Functor: Functor2URI { URI, map: _map } export const Comonad: Comonad2URI { URI, map: _map, extend: _extend, extract }其中_map/_extend是/* istanbul ignore next */標(biāo)記的非柯里化適配層src/Store.ts把柯里化 API 轉(zhuǎn)成實(shí)例所需的二元簽名。抽象層級(jí)關(guān)系如下對(duì)應(yīng) src/Comonad.ts 與 src/Extend.ts 的接口定義Comonad2W extends Extend2W extends Functor2W ├── map : 來自 Functor ├── extend : 來自 Extend └── extract: Comonad 新增唯一取出操作Comonad2接口要求extract: E, A(wa: Kind2W, E, A) Asrc/Comonad.ts正好與Store的extract對(duì)應(yīng)。七、zone of death被廢棄的 store 實(shí)例文檔最后一部分是zone of death廢棄區(qū)store自 v2.0.0 起存在、現(xiàn)已標(biāo)記deprecatedexport declare const store: Comonad2Store廢棄原因源碼注釋 src/Store.ts 與文檔一致應(yīng)改用小而具體的實(shí)例。例如某個(gè)函數(shù)需要Comonad時(shí)傳入S.Comonad而不是S.store其中S來自import S from fp-ts/Store。這符合 fp-ts v2 中期以來的 API 演進(jìn)方針——用Functor、Comonad等最小實(shí)例按需組合避免一次性傳遞超大實(shí)例。新代碼應(yīng)直接使用Comonad。八、實(shí)例與定律驗(yàn)證從測(cè)試看正確性test/Store.ts 使用describe/it組織測(cè)試通過pipe管道式調(diào)用覆蓋了全部核心 API被測(cè)函數(shù)測(cè)試位置關(guān)鍵斷言maptest/Store.tsextract(map(n n1)(wa)) 2extendtest/Store.ts鄰域求值后再extract得到2duplicatetest/Store.tsextract(extract(duplicate(wa))) 1余單子定律seektest/Store.ts焦點(diǎn)移到aa后extract 2seekstest/Store.ts變換(s) s a后extract 2peekstest/Store.ts不移動(dòng)焦點(diǎn)窺探aa得2experimenttest/Store.ts與RA.Functor配合得[1, 2]這些用例同時(shí)驗(yàn)證了余單子的關(guān)鍵定律extract 左單位律extract ° duplicate idduplicate測(cè)試map/extend 與 extract 的相容性extract ° map(f) f ° extractextend 的復(fù)合extend(f) map(f) ° duplicate。借助pipe定義于 src/function.tsStore的 API 全部采用數(shù)據(jù)在管道尾部的柯里化形式可讀性良好。九、完整實(shí)戰(zhàn)用 Store 模擬一個(gè)帶光標(biāo)的小文本緩沖區(qū)綜合以上 API構(gòu)造一個(gè)字符串長(zhǎng)度緩沖區(qū) 光標(biāo)的完整示例import * as _ from fp-ts/Store import { pipe } from fp-ts/function import * as RA from fp-ts/ReadonlyArray import * as S from fp-ts/string // 1. 創(chuàng)建光標(biāo)在 abc 開頭 const buffer: _.Storestring, number { peek: S.size, pos: a } // 2. extract讀取當(dāng)前焦點(diǎn)處的長(zhǎng)度 pipe(buffer, _.extract) // 1 // 3. seek直接跳到 hello pipe(buffer, _.seek(hello), _.extract) // 5 // 4. seeks相對(duì)移動(dòng)向后追加一個(gè)字符 pipe(buffer, _.seeks((s) s !), _.extract) // 2 // 5. peeks不移動(dòng)光標(biāo)窺探右側(cè)一個(gè)位置 pipe(buffer, _.peeks((s) s !)) // 2buffer.pos 仍為 a // 6. experiment批量探測(cè)當(dāng)前位置及其鄰域 pipe(buffer, _.experiment(RA.Functor)((s) [s, s a, s aa])) // [1, 2, 3] // 7. map把讀取結(jié)果變換后仍保持焦點(diǎn) pipe(buffer, _.map((n) length${n}), _.extract) // length1 // 8. extend在每個(gè)位置計(jì)算當(dāng)前位置與其下一個(gè)位置長(zhǎng)度之和 const sumWithNext pipe( buffer, _.extend((wa) _.extract(wa) _.peeks((s) s a)(wa)) ) pipe(sumWithNext, _.extract) // 1 2 3該示例完整串聯(lián)了文檔列出的全部核心函數(shù)extract、seek、seeks、peeks、experiment、map、extend可作為日常使用Store的速查樣板。十、何時(shí)使用 Store適用場(chǎng)景小結(jié)結(jié)合文檔內(nèi)容與源碼結(jié)構(gòu)Store適合以下場(chǎng)景帶焦點(diǎn)的只讀上下文編輯器光標(biāo)、地圖/棋盤坐標(biāo)、表格選中單元格等當(dāng)前位置 全局查詢能力的組合局部計(jì)算需要依賴鄰域一維/二維元胞自動(dòng)機(jī)、圖像卷積、平滑濾波——extend讓每個(gè)點(diǎn)的值由周圍環(huán)境決定得以純函數(shù)式表達(dá)與 Comonad 抽象協(xié)同需要extract/extend/duplicate三件套參與泛型組合的算法如 Conway 生命游戲可以建模為StoreCell, Cell上的迭代只想偷看不改狀態(tài)peeks/experiment提供無(wú)副作用的位置探測(cè)。需要注意Store是純只讀結(jié)構(gòu)seek/seeks產(chǎn)生的是新的 Store 而非原地修改若需要可寫狀態(tài)應(yīng)配合其他狀態(tài)管理模塊使用。附錄API 速查表分類名稱簽名要點(diǎn)版本Extractextract(wa: StoreE, A) A讀取當(dāng)前焦點(diǎn)值v2.6.2instancesComonadComonad2Store含 map/extend/extractv2.7.0instancesFunctorFunctor2Store僅含 mapv2.7.0mappingflap把值應(yīng)用到容器內(nèi)函數(shù)v2.10.0mappingmap在保持pos的同時(shí)變換peek結(jié)果v2.0.0modelStore{ peek: (s: S) A; pos: S }v2.0.0type lambdasURI/URI字符串Store及 HKT 注冊(cè)v2.0.0utilsduplicateextend(identity)產(chǎn)生StoreE, StoreE, Av2.0.0utilsexperiment基于當(dāng)前位置批量探測(cè)并聚合v2.0.0utilsextend將上下文 → 值的計(jì)算推廣到每個(gè)位置v2.0.0utilspeeks不移動(dòng)焦點(diǎn)窺探目標(biāo)位置v2.0.0utilsseek焦點(diǎn)移到指定位置v2.0.0utilsseeks依據(jù)變換函數(shù)重定位焦點(diǎn)v2.0.0zone of death~~store~~已廢棄改用Comonad等小實(shí)例v2.0.0版本說明以上版本號(hào)均取自當(dāng)前倉(cāng)庫(kù) src/Store.ts 與 docs/modules/Store.ts.md 中的since標(biāo)注。延伸閱讀Comonad 抽象定義了解Comonad2等接口的層級(jí)關(guān)系Extend 抽象定義extend運(yùn)算的形式化約束Functor 模塊flap的通用實(shí)現(xiàn)src/Functor.tsEndomorphism 定義seeks/peeks依賴的(s: S) S類型Store 源碼 與 Store 測(cè)試本文全部結(jié)論的實(shí)現(xiàn)與驗(yàn)證依據(jù)?!久赓M(fèi)下載鏈接】fp-tsFunctional programming in TypeScript項(xiàng)目地址: https://gitcode.com/gh_mirrors/fp/fp-ts創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考