首页/新闻资讯/正文详情

深入解析 lann/builder:用 Go 编写不可变、可复用的流式 Builder DSL

发布时间:2026/9/24 13:36:08 来源:云帆数科 栏目:资讯中心
深入解析 lann/builder:用 Go 编写不可变、可复用的流式 Builder DSL
人工智能AI AgentAgent 沙箱云原生容器运行时零信任【免费下载链接】substrateAgent Substrate: the core system项目地址https://gitcode.com/GitHub_Trending/substrate7/substrate点击查看免费下载Builder 是 Go 语言中一套面向“流式fluent不可变构建器”的底层工具库本仓库将其以 vendor 方式内置在vendor/github.com/lann/builder并同时携带其持久化数据结构依赖vendor/github.com/lann/ps。读完本文你将掌握 Builder 的核心 APISet、Append、Extend、Get、GetStruct等、注册机制Register/RegisterType、底层不可变数据结构的实现原理以及它如何支撑起 Squirrel 这类流式 SQL 生成器——并且可以立即在自己的库中复刻同样的模式。一、Builder 要解决什么问题在 Go 里当我们想让 API 调用者以“链式调用”的方式配置一个复杂对象时最自然的写法是resp : ReqBuilder. Url(http://golang.org). Header(User-Agent, Builder). Get()这种风格被称为 fluent DSL。它的问题在于如果每一步都直接修改同一个内部结构体那么中间状态会被破坏——比如两个调用者共享同一个build : WordBuilder.AddLetters(Build)其中一个继续追加er另一个追加ing如果结构体是可变的两者就会互相污染。Builder 的核心主张是每一步链式调用都返回一个全新的、与原状态共享底层结构的新实例从而实现“每个中间步骤都可以安全复用”build : WordBuilder.AddLetters(Build) builder : build.AddLetters(er) building : build.AddLetters(ing)上面的例子中builder得到Builderbuilding得到Building而build自身仍然是Build——这正是“不可变immutable”语义的价值。二、不可变的基石lann/ps 持久化数据结构“不可变”不是靠每次全量拷贝实现的那会带来 O(N) 的复制开销Builder 选择的是持久化数据结构persistent data structures。其依赖来自vendor/github.com/lann/ps该包是github.com/mndrix/ps的稳定 fork见 vendor/github.com/lann/ps/README.md。ps.Map是一个字符串到任意值的持久化关联数组接口定义在 vendor/github.com/lann/ps/map.goSet(key, value)返回新 map不修改原 mapO(log N)Delete(key)返回移除该键后的新 mapO(log N)Lookup(key)返回(value, bool)O(log N)Size()O(1) 返回键值对数量ForEach(f)、Keys()遍历辅助。从实现上看ps.Map是一棵路径拷贝path-copying哈希树每个节点固定拥有 8 个子树childCount 8键通过 FNV-1a 哈希见hashKey逐 3 位分片shiftSize 3向下路由Set时只克隆从根到叶子的那条路径上的节点setLowLevel中的m : self.clone()其余子树与原树共享因此时间与空间开销都与树高成正比而不是与整个 map 的大小成正比。空树nilMap的所有子树都指向自身从而消除了全部空指针。ps.List则是一个持久化单向链表vendor/github.com/lann/ps/list.goCons(val)以 O(1) 代价在头部插入新节点并返回新链表新节点共享原链表作为尾部nilList作为所有空链表的共享尾部。注意它是头插法因此 Builder 在把 list 还原成 slice 时会倒序回填见下文Get部分。三、核心数据结构与基础操作Builder本体定义在 vendor/github.com/lann/builder/builder.gotype Builder struct { builderMap ps.Map }它内部只持有一个ps.Map所有命名值都存在这个 map 里。包级变量EmptyBuilder是唯一的空构建器起点var ( EmptyBuilder Builder{ps.NewMap()} emptyBuilderValue reflect.ValueOf(EmptyBuilder) )3.1 Set 与 Delete写入与移除命名值func Set(builder interface{}, name string, v interface{}) interface{} func Delete(builder interface{}, name string) interface{}Set调用ps.Map.Set得到新 map再包装成新的Builder并通过reflect.Value.Convert转换回调用者的自定义 builder 类型返回convert定义在 vendor/github.com/lann/builder/reflect.go。因此原 builder 完全不变返回的是副本。Delete同理用于移除某个命名值。源码注释明确约定所有接收 builder 的函数若底层类型不是Builder会直接 panic。3.2 Append 与 Extend追加列表值func Append(builder interface{}, name string, vs ...interface{}) interface{} func Extend(builder interface{}, name string, vs interface{}) interface{}Append本质是Extend的变参形式将多个值追加到命名列表Extend则接受任意类型的 slice/array通过reflect.ValueOf(vs).Len()遍历见forEach。两者的内部逻辑builder.go若传入值为 nil直接返回原 builder从 map 中查找该名字对应的ps.List若不存在或类型不是ps.List则新建空列表逐个Cons新值头插用Set写回新 map。由于是头插元素在内部是逆序存储的最终输出时会统一反转。3.3 Get 与 GetMap读取构建结果func Get(builder interface{}, name string) (interface{}, bool) func GetMap(builder interface{}) map[string]interface{}Get返回单个命名值若该值是用Append/Extend写入的ps.List则会调用listToSlice把它转换成 slicebuilder.go从size-1倒序回填把链表的头插顺序还原为追加顺序。默认 slice 类型是[]interface{}如果该名字是已注册结构体的导出字段slice 会被转成对应字段的类型如[]string。GetMap则一次性返回所有命名值的map[string]interface{}。四、注册机制把 Builder 变成结构体工厂4.1 Register / RegisterType要让GetStruct工作必须先把 builder 类型与目标结构体类型“注册”起来。注册逻辑在 vendor/github.com/lann/builder/registry.gofunc RegisterType(builderType reflect.Type, structType reflect.Type) *reflect.Value func Register(builderProto, structProto interface{}) interface{}RegisterType内部用sync.RWMutex保护的registry map[reflect.Type]reflect.Type记录映射并会调用structType.NumField()来确保传入的确实是结构体类型否则 panicRegister是RegisterType的便捷包装传入两个实例返回一个可作链式起点的空 builder 实例底层是EmptyBuilder转换而成。4.2 GetStruct / GetStructLike从 builder 装配结构体func GetStruct(builder interface{}) interface{} func GetStructLike(builder interface{}, strct interface{}) interface{}两者都通过scanStructbuilder.go完成装配遍历 builder 中所有命名值只处理名字以大写字母开头ast.IsExported即“如果它是标识符就属于导出”的值按名字匹配结构体字段对于ps.List直接listToSlice成对应字段类型对于nil仅当字段类型为 chan/func/interface/map/ptr/slice 之一时置零值否则field.Set会 panic其余值直接reflect.ValueOf后field.Set。GetStruct要求该 builder 类型已经注册否则返回 nilGetStructLike则不必注册直接以传入的strct实例的类型为目标。五、实战用 10 行代码定义自己的流式 Builder以下是原 README 的完整示例已随仓库 vendor 在 vendor/github.com/lann/builder/README.md它演示了定义 builder 的完整套路——声明一个底层类型为builder.Builder的新类型然后在方法里调用包级函数并做类型断言import github.com/lann/builder type Muppet struct { Name string Friends []string } type muppetBuilder builder.Builder func (b muppetBuilder) Name(name string) muppetBuilder { return builder.Set(b, Name, name).(muppetBuilder) } func (b muppetBuilder) AddFriend(friend string) muppetBuilder { return builder.Append(b, Friends, friend).(muppetBuilder) } func (b muppetBuilder) Build() Muppet { return builder.GetStruct(b).(Muppet) } var MuppetBuilder builder.Register(muppetBuilder{}, Muppet{}).(muppetBuilder)使用效果MuppetBuilder. Name(Beaker). AddFriend(Dr. Honeydew). Build() Muppet{Name:Beaker, Friends:[]string{Dr. Honeydew}}拆解这段套路type muppetBuilder builder.Builder让自定义类型拥有Builder的底层布局从而可以被包级函数接收并转换每个 setter 返回muppetBuilderbuilder.Set/builder.Append返回interface{}必须断言回具体类型这是 fluent 链能够继续下去的关键Build()调用builder.GetStruct借助注册表把命名值装配进Muppet结构体builder.Register(muppetBuilder{}, Muppet{})完成类型注册并生成链式起点注意Friends是[]string而Append写入的是ps.List最终GetStruct会依据注册的字段类型把它还原成[]string——这正是“注册”这一步骤必不可少的原因。AddFriend的多次调用会不断追加AddFriend(A).AddFriend(B)最终得到Friends: []string{A, B}。每次Append都产生新 map中间状态可自由复用天然规避了可变结构体共享带来的 bug。六、真实世界的范例Squirrel 流式 SQL 生成器README 明确指出Builder 最初就是为Squirrel——一个流式 SQL 生成器——而写的是它最典型的使用案例。本仓库的 vendor 目录中恰好完整保留了 Squirrelvendor/github.com/Masterminds/squirrel/可以直接对照学习。以 vendor/github.com/Masterminds/squirrel/squirrel.go 为例Squirrel 内部正是通过builder.Set存储RunWith等配置项return builder.Set(b, RunWith, runner)而在 vendor/github.com/Masterminds/squirrel/select.go、vendor/github.com/Masterminds/squirrel/insert.go、vendor/github.com/Masterminds/squirrel/update.go、vendor/github.com/Masterminds/squirrel/delete.go 以及各自的_ctx.go变体中处处可见builder.Set、builder.Append、builder.GetStruct的身影。Squirrel 的典型用法users : sq.Select(*).From(users).Where(sq.Eq{name: Beaker})Select(...)返回的SelectBuilder本质上就是一个注册过的 builder 类型Where、From、Join等每步都返回新实例最终ToSql()内部调用builder.GetStruct取出完整状态并渲染成 SQL。这意味着中间任意一步都可以保存下来、分支复用——比如基础查询对象被多个场景追加不同的过滤条件。七、使用注意事项与约束从源码中可以提炼出以下几条明确约束见各函数注释底层类型必须是 BuilderSet、Get、GetStruct等函数若收到底层类型不是Builder的值会 panic自定义 builder 类型必须用type X builder.Builder声明。导出字段才生效GetStruct/GetStructLike只把名字以大写字母开头的命名值写入结构体对应字段小写开头的命名值会被忽略。类型不匹配会 panic若某命名值无法赋值给注册结构体的对应字段如把字符串赋给 int 字段field.Set会 panicnil值也只对 chan/func/interface/map/ptr/slice 这类可置零的字段合法。不可变性的边界Builder 本身不可变但放入的值若本身是可变对象如*bytes.Buffer仍需使用者自己保证不在使用期间被修改——源码注释对此有明确提醒。注册是全局的registry是包级 mapRegister后全局生效同一 builder 类型不可重复注册到不同结构体。八、许可证Builder 采用 MIT License 发布见 vendor/github.com/lann/builder/LICENSE其依赖lann/ps同样为 MIT 许可vendor/github.com/lann/ps/LICENSE可放心在商业项目中集成使用。小结lann/builder用约两百行核心代码把“流式调用 不可变中间态 反射装配结构体”三件事封装成了清晰的小型 APIlann/ps提供持久化 map/list 作为不可变基石Set/Append/Extend负责写入Get/GetMap/GetStruct负责读取与装配Register负责建立 builder 与结构体之间的类型映射。掌握它之后你既能读懂 Squirrel 的整套 fluent SQL 实现也能在 10 行代码内为自己的库定制同样优雅的链式 DSL。赞分享人工智能AI AgentAgent 沙箱云原生容器运行时零信任【免费下载链接】substrateAgent Substrate: the core system项目地址https://gitcode.com/GitHub_Trending/substrate7/substrate点击查看免费下载相关推荐深入解析 lann/builder为 Go 库构建不可变链式 DSL 的通用基础设施深入解析 lann/builder为 Go 库构建不可变链式 DSL 的通用基础设施 导读 lann/builder 是一个专为 Go 语言设计的通用「构建器后端云原生容器编排微服务Cilium 仓库中的 Go 流式不可变 Builder 库lann/builder源码级解析Cilium 仓库中的 Go 流式不可变 Builder 库lann/builder源码级解析 导读 vendor/github.com/lann/buil云原生网络服务网格可观测性网络安全eBPFKubeSphere 依赖树中的 Go 流式不可变构建器lann/builder 源码精读KubeSphere 依赖树中的 Go 流式不可变构建器lann/builder 源码精读 本篇以 KubeSphere 仓库 vendor 目录中引入的 l云原生容器编排后端微服务多集群DevOps可观测性AI 技能上一篇three.js TubeGeometry 详解沿 3D 曲线扫掠生成管道网格几何体下一篇Bench更强大的命令行基准测试工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

烘焙后城市场景满是黑斑?用6步检查 Lightmap UV 与光照接缝
烘焙后城市场景满是黑斑?用6步检查 Lightmap UV 与光照接缝

城市场景完成光照烘焙后,如果出现整面发黑、局部脏斑、模块接缝发亮,先不要急着提高灯光强度。更常见的原因是 Lightmap UV 重叠、UV 岛间距不足、光照贴图分辨率与对象尺寸不匹配,以及薄面、法线或模块边界存在问题。 本文用一个最小场景演… · 2026/9/24 13:36:08

openFrameworks 粒子系统实战:particlesExample 四种交互模式与源码级解析
openFrameworks 粒子系统实战:particlesExample 四种交互模式与源码级解析

图形学音视频 【免费下载链接】openFrameworks openFrameworks is a community-developed cross platform toolkit for creative coding in C. 项目地址: https://gitcode.com/gh_mirrors/op/openFrameworks 点击查看 免费下载 本文以 openFrameworks 官方示例 exa… · 2026/9/24 13:36:01

models 仓库 AlexNet ONNX 模型全解析:从模型清单、预处理到 int8 量化实战
models 仓库 AlexNet ONNX 模型全解析:从模型清单、预处理到 int8 量化实战

人工智能大模型计算机视觉NLP模型评测 【免费下载链接】models A collection of pre-trained, state-of-the-art models in the ONNX format 项目地址: https://gitcode.com/gh_mirrors/model/models 点击查看 免费下载 AlexNet 是 2012 年 ImageNet 大规模视觉识… · 2026/9/24 13:36:01

AI会“躲检查”了,自查还靠得住吗?
AI会“躲检查”了,自查还靠得住吗?

AI会“躲检查”了,自查还靠得住吗?过去,AI出错人类还能发现。现在,智能体会规划任务、调用工具、隐藏痕迹,风险从“说错话”变成“越权访问、泄露数据、清理痕迹”。有测试称,一组AI智能体攻破Hugging Face… · 2026/9/24 14:03:37

BesTV R3300-L刷机全攻略:S905L平台线刷避坑指南
BesTV R3300-L刷机全攻略:S905L平台线刷避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 14:03:31

【Dv3Admin】视图下全部权限按钮批量生成
【Dv3Admin】视图下全部权限按钮批量生成

在现代 Web 应用开发中,权限管理是确保系统安全性和功能灵活性的核心部分。每个功能模块通常会根据不同的角色需求,提供不同的权限操作按钮,如:查看、编辑、删除等。而在实际开发中,当功能模块数量庞大时,手动为每个功能模块创建权限项是一项重复且繁琐的工作。 本文将介… · 2026/9/24 14:03:31

乐观帧(Optimistic Lockstep):服务器按节拍走,到底好在哪、坏在哪
乐观帧(Optimistic Lockstep):服务器按节拍走,到底好在哪、坏在哪

一句话定位 严格锁步:服务器等所有人的输入到齐 → 才推进一帧 乐观帧 :★ 服务器按固定节拍推进 → 谁没到,用预测填"乐观"二字的含义是:我乐观地假设你的输入马上就到;没到我也不等,先按我猜的… · 2026/9/24 14:03:31

长按5秒开机背后的防误触设计逻辑与硬件实现
长按5秒开机背后的防误触设计逻辑与硬件实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 14:03:25

Ekko Agent Apple Notes Skill 实战:在 macOS 上用 memo CLI 管理备忘录的完整指南
Ekko Agent Apple Notes Skill 实战:在 macOS 上用 memo CLI 管理备忘录的完整指南

AI 应用人工智能AI Agent本地部署前端后端工作流自动化 【免费下载链接】ekko-studio Ekko Studio is a local-first AI workspace for multi-agent chat, coding, and visual workflows, available on desktop and the web. 项目地址: https://gitcode.com/gh_mirr… · 2026/9/24 14:03:25

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13

1D-CNN时间序列建模实战:从Conv1d原理到工业落地
1D-CNN时间序列建模实战:从Conv1d原理到工业落地

简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26

柔软的L:汉语语流中被忽视的舌肌张力控制
柔软的L:汉语语流中被忽视的舌肌张力控制

1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码