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