effect-smol Optic 函数式光学:Lens、Prism 与 Traversal 完整指南
2026/9/20 13:47:48 网站建设 项目流程

effect-smol Optic 函数式光学:Lens、Prism 与 Traversal 完整指南

【免费下载链接】effect-smolCore libraries and experimental work for Effect v4项目地址: https://gitcode.com/GitHub_Trending/ef/effect-smol

如果你在用 TypeScript 处理嵌套不可变数据,一定被"改一个字段要重写整棵对象树"折磨过。effect-smol项目中的Optic 模块(函数式光学)正是解药:它让你像相机变焦一样,用Lens、Prism、Traversal等"光学元件"安全地聚焦、读取和更新嵌套结构中的任意部分——且全程不修改原数据。

🎯 什么是函数式光学?

把光学想象成一个可复用的"聚焦器":它描述了"在哪里看、在哪里改"。每个光学都支持三个核心操作:

操作含义类比
get读取聚焦到的值用放大镜看细节
replace替换聚焦值换掉那颗螺丝
modify用函数变换聚焦值把螺丝拧紧一点

构建方式像搭积木:先聚焦company,再聚焦address,再聚焦street,层层下钻。定义一次,到处复用。

📖 官方文档:packages/effect/OPTIC.md 🔧 核心源码:packages/effect/src/Optic.ts

🧩 五种光学:一分钟认识家族成员

Optic 模块提供了五种光学类型,它们共享同一套组合方式:

光学聚焦目标聚焦后一定有值吗?典型场景
Iso无损可逆的类型转换✅ 必有摄氏度 ↔ 华氏度
Lens一定存在的字段✅ 必有user.name
Prism联合类型的某一个分支⚠️ 可能没有匹配_tag: "A"
Optional可能存在的焦点⚠️ 可能没有可选字段、数组下标
Traversal集合中的零或多个元素⚠️ 多个遍历数组所有正数

一句话记忆:Iso 最强、Lens 最常用、Prism 管联合、Traversal 管批量

类型定义可以在源码中查看:

  • Iso接口:packages/effect/src/Optic.ts#L66
  • Lens接口:packages/effect/src/Optic.ts#L144
  • Prism接口:packages/effect/src/Optic.ts#L232
  • Traversal接口:packages/effect/src/Optic.ts#L1033

⚡ 快速上手:三行代码聚焦字段

先看最小示例——聚焦对象S中的字段a

import { Optic } from "effect" type S = { readonly a: number } const _a = Optic.id<S>().key("a") _a.get({ a: 1 }) // 1 读取 _a.replace(2, { a: 1 }) // { a: 2 } 替换 _a.modify((n) => n + 1)({ a: 1 }) // { a: 2 } 变换

注意Optic.id<S>():它是"身份光学"(identity Iso),表示"先聚焦整个对象本身",是所有光学的起点。

嵌套数据:层层下钻到深层字段

假设要修改员工信息中「公司 → 地址 → 街道 → 名称」这个深埋的字段:

const _streetName = Optic.id<Employee>() .key("company") // 进入 company .key("address") // 进入 address .key("street") // 进入 street .key("name") // 锁定 name const capitalizeStreetName = _streetName.modify(String.capitalize)

调用capitalizeStreetName(from)后,只有street.name的首字母被大写,其余数据原封不动,且原对象保持不可变。路径只定义一次,改数据结构时只需改这一个地方。

🔍 Lens 实战:安全处理可选字段

Lens 聚焦"一定存在"的字段,但 Optic 对可选数据也有声明式处理,关键是区分两种语义:

  • key("a"):值为undefined保留该键 →{ a: undefined }
  • optionalKey("a"):值为undefined删除该键 →{}
const _a = Optic.id<S>().optionalKey("a") _a.replace(2, {}) // { a: 2 } 从"无"到"有" _a.replace(undefined, { a: 1 }) // {} 从"有"到"无"(键被移除)

这套语义让"清除字段"这类操作变得干净利落,无需手写delete或解构技巧。

🔺 Prism 实战:安全聚焦联合类型的一个分支

联合类型(tagged union)是函数式编程的常客。直接用key访问某个分支的字段会踩空,而Prism通过tag方法聚焦特定分支:

type S = | { readonly _tag: "A"; readonly a: number } | { readonly _tag: "B"; readonly b: number } const _a = Optic.id<S>().tag("A").key("a") _a.replace(2, { _tag: "A", a: 1 }) // { _tag: 'A', a: 2 } 匹配,更新 _a.replace(2, { _tag: "B", b: 1 }) // { _tag: 'B', b: 1 } 不匹配,原样返回

约定用_tag字段标识变体。不匹配时优雅地无操作,而不是抛错——这让联合类型的更新既安全又可预测。

🚂 Traversal 实战:批量处理集合元素

Traversal聚焦集合中的多个元素。配合forEach+ 条件判断,可以精准批处理:

const _positive = Optic.id<S>() .key("a") // 聚焦数组 .forEach((item) => item.check(Schema.isGreaterThan(0))) // 只保留正数 const addOne = _positive.modifyAll((n) => n + 1) addOne({ a: [1, -2, 3] }) // { a: [2, -2, 4] } 负数不受影响

📌 技术细节:与其他光学库不同,这里的Traversal不是独立类型,而是被建模为"聚焦到ReadonlyArray<A>Optional"。要对每个元素继续下钻,通过forEach传入的Iso<A, A>与后续光学组合即可。

🧬 进阶:从 Schema 一键生成光学

如果你有 Effect 的 Schema,可以直接生成光学起点,省去手写路径:

import { Schema } from "effect" const schema = Schema.Struct({ a: Schema.String, b: Schema.Number }) const _b = Schema.toIso(schema).key("b") // 自动获得类型安全的 Lens

Schema.Class也开箱即用,类实例可以直接作为光标的操作对象。相关测试见 packages/effect/test/schema/toIso.test.ts。

⚖️ 既然有 Immer,为什么还要 Optic?

Immer 让"写可变代码、得到不可变结果",日常够用。但当你面对深层嵌套 + 联合类型 + 可复用更新逻辑时,Optic 的优势很明显:

维度ImmerOptic
路径导航每次更新都手写一遍定义一次 Lens,到处复用
可选字段手动if守卫notUndefined()声明式表达
结构演化散落各处的路径同步修改改一处光学定义,全局生效

核心哲学是组合胜过嵌套:把小光学拼成大光学,代码保持扁平、可测试、可跨模块共享。

📋 选型速查表

不知道怎么选光学?对照这张表:

数据形状用这个
一定存在的字段key
可选字段(保留undefinedkey
可选字段(清除时删键)optionalKey
联合类型的某个分支tag
Record / 数组下标at
过滤并批量更新元素forEach+check/notUndefined

另外,如果聚焦失败需要排查,用getResult显式拿到"成功/失败"结果,比静默跳过更易调试。

🚀 开始学习 Optic

资源路径
官方完整文档(含全部示例)packages/effect/OPTIC.md
核心实现(类型 + 构造函数 + 操作)packages/effect/src/Optic.ts
单元测试(行为参考)packages/effect/test/Optic.test.ts
Schema 生成光学测试packages/effect/test/schema/toIso.test.ts

上手路线建议:先用key链式聚焦嵌套字段建立手感 → 再用tag处理联合类型 → 最后用forEach+check玩转批量更新。配合 packages/effect/OPTIC.md 中的完整示例,你就能在不可变数据的世界里自由"变焦"了。

【免费下载链接】effect-smolCore libraries and experimental work for Effect v4项目地址: https://gitcode.com/GitHub_Trending/ef/effect-smol

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询