Ferret 模块体系深度解析:Module 引导、SDK 作者层与标准库分组架构
网页爬虫后端开发工具【免费下载链接】ferretDeclarative data automation language and Go runtime for structured extraction workflows.项目地址https://gitcode.com/gh_mirrors/fe/ferret点击查看免费下载Ferret 是一个用于结构化数据提取工作流的声明式数据自动化语言与 Go 运行时。本文围绕 Modules, SDK, and Standard Library 展开系统讲解其扩展体系的分层设计pkg/module的模块引导契约与生命周期钩子、pkg/sdk的作者辅助层、以及pkg/stdlib的标准库分组注册机制并深入剖析 Math、Random 等核心能力组的实现语义。读完本文你将掌握如何为 Ferret 编写自定义模块、注册宿主函数、理解钩子执行顺序以及如何通过能力组选择精确裁剪内建函数集。分层边界为什么模块、SDK 与标准库必须分离Ferret 的扩展能力被刻意划分为四个职责不同的层次而不是混在一个插件系统里稳定模块契约pkg/module引擎级扩展的正式接口提供宿主服务注册与生命周期钩子注册能力受支持的作者辅助层pkg/sdk面向模块作者与宿主值host value作者的便捷封装全部建立在公开契约之上内建库注册pkg/stdlibFerret 自带函数、命名空间与不可变能力组选择的归属地运行时持有的语义pkg/runtime值模型、比较、迭代、资源所有权等共享语义的唯一权威。文档明确强调保持这些层次分离可以让嵌入方embedder精确控制能力边界同时避免把 stdlib 的实现细节变成模块 API。这是一条双向约束——可复用的模块契约不应进入 stdlib而 stdlib 特有的行为也不应进入pkg/module。Module 引导引擎扩展的正式契约Module 接口与注册时机pkg/module.Module是引擎扩展的可复用契约源码定义极其精简pkg/module/module.gotype Module interface { // Name 返回该模块的稳定标识符。 Name() string // Register 修改该模块实例的引擎引导状态。 // 返回错误会中止引擎构建。 Register(Bootstrap) error }一个模块拥有稳定名称并在ferret.New构建引擎期间向module.Bootstrap注册。注册流程在引擎宿主最终确定之前完成注册一旦出错整个引擎构建立即中止并触发对已创建资源的清理。从源码结构可以推断这一注册即失败即回滚的语义与pkg/engine/internal/bootstrap.Build的构建回滚机制协同工作——构建失败会关闭未转移的资源管理器。Bootstrap 暴露的两类能力Bootstrap接口pkg/module/bootstrap.go只暴露两个入口type Bootstrap interface { Host() HostContext // 宿主作用域的注册表与服务 Hooks() HookRegistrar // 引擎、计划、会话生命周期钩子注册器 }Bootstrap.Host()返回HostContextpkg/module/host.go涵盖六类宿主作用域能力能力类型说明运行时函数库runtime.Library引擎正在组装的运行时库注册表默认参数runtime.Params由引擎继承给所有会话的默认参数集编解码器注册encoding.CodecRegistrar配置引擎的输出编码日志logging.Logger引擎日志器会话继承文件系统fs.FileSystem引擎及派生执行使用的文件系统网络服务ferretnet.Network引擎及派生执行使用的网络服务Bootstrap.Hooks()返回HookRegistrar按引擎、计划、会话三个阶段分别暴露注册器pkg/module/hooks.go。文档给出了一条重要的使用准则模块注册是配置这些共享服务与回调的地方而不是保留属于会话的可变执行状态的地方。也就是说模块在Register阶段应当完成布线把状态交给引擎、计划或会话生命周期管理而非在模块对象上持有跨会话的可变数据。钩子生命周期顺序即契约三类钩子与注册位置钩子按生命周期分为三层引擎钩子覆盖初始化OnInit与关闭OnClose计划钩子覆盖编译前BeforeCompile、每次编译尝试后AfterCompile与计划关闭OnClose会话钩子覆盖每次运行前BeforeRun、每次运行后AfterRun与会话关闭OnClose。从钩子函数签名pkg/module/hooks.go可以精确看到每个回调的职责type EngineInitHook func() error type EngineCloseHook func() error type BeforeCompileHook func(ctx context.Context) error type AfterCompileHook func(ctx context.Context, err error) error type PlanCloseHook func() error // BeforeRunHook 可返回一个派生上下文供后续钩子与 VM 执行使用。 type BeforeRunHook func(ctx context.Context) (context.Context, error) type AfterRunHook func(ctx context.Context, err error) error type SessionCloseHook func() error顺序规则顺序是钩子契约的一部分文档明确规定了五条规则init、before-compile、before-run钩子**按注册顺序FIFO**执行after-compile、after-run、close钩子**按注册逆序LIFO**执行before 钩子在第一个错误处停止after 钩子接收主操作错误即编译错误或运行错误close 路径聚合错误并继续清理——即某个 close 钩子失败不会中断后续清理。Before/After 钩子的配对语义before-run钩子可以返回一个派生上下文供后续钩子与 VM 执行使用。文档给出了一个非常关键的执行保证一旦所有 before-run 钩子都成功after-run 钩子恰好运行一次——即使后续的上下文校验阻止了 VM 进入。它们接收的是钩子返回的上下文如果钩子上下文为 nil则接收调用方原始上下文同时接收相同的执行错误或校验错误钩子自身的失败会与该错误连接join。而预先取消的准入pre-canceled admission与失败的 before-run 钩子不会触发 after-run 钩子。这些规则对普通会话与调试会话debug session同样适用。失败不影响输出after-run钩子失败不会丢弃已成功编码的输出。普通会话在编码与结果清理之前运行这些钩子返回的错误会聚合钩子、编码与清理三方面的失败但不会使可用输出失效。这与 runtime.md 中Session.Run在 after-run 钩子或结果清理失败时仍保留成功编码的输出的描述一致。SDK 作者层薄适配不搬核心实现SDK 提供的工具面pkg/sdk是模块与宿主值作者的受支持便捷层pkg/sdk/doc.go覆盖回调驱动的模块NewModule声明式函数定义RegisterFunctions支持按 arity 重载类型化运行时绑定器Bind0–Bind4、Bind上下文感知的编解码器Encode、Decode、DecodeValue、DecodeArg、Codec宿主值包装器HostValue与集合视图IterableValue、IteratorValue、SliceView、MapView黑盒测试工具pkg/sdk/sdktest。设计约束非常明确SDK 辅助函数应当保持为对公开模块与运行时契约的薄适配层。核心实现细节不会仅仅为了让包间可访问就搬进pkg/sdk——这防止了 SDK 沦为绕过架构边界的后门。RegisterFunctions 的原子校验RegisterFunctions是 SDK 注册函数的核心入口其实现pkg/sdk/function_registration.go体现了几个关键语义校验先于变更先完整校验整个注册集合任何定义非法则整个注册集合并不产生任何变更原子性名称规范化函数名含命名空间段被规范化canonicalized为小写FQL 中以大小写不敏感方式解析——同名不同大小写在 FQL 中指向同一个规范名称固定 arity 可互相重载固定元数的函数可以互相重载arity 0–4 分别进入A0()–A4()注册表可变参数可同名并存一个 variadic 回退runtime.Function对应 arity 标记0xff可与固定元数函数共享名称重复名称/元数定义被原子拒绝校验器通过functionRegistrationKey{name, arity}去重既检查集合内部重复也检查命名空间已有注册。// 固定元数如 Function2由 Bind2 产生 // 可变参数函数由 Bind 产生注册进 Var() 表。 func registerFunctionDefinition(functions runtime.FunctionDefs, definition FunctionDef) { switch fn : definition.function.(type) { case runtime.Function: functions.Var().Add(definition.name, fn) case runtime.Function0: functions.A0().Add(definition.name, fn) // ... Function1 至 Function4 对应 A1() 至 A4() } }类型化绑定器面向 runtime.Value而非反射Bind0–Bind4与Bindpkg/sdk/bind.go把类型化 Go 函数适配为 Ferret 宿主函数。它们的实现原则是只对runtime.Value约束运行通过runtime.CastArg委托参数转换而不是用反射reflection调用任意 Go 函数转换失败返回带参数位置的错误如CastArgA的0即第一个参数结果通过normalizeBoundResult处理 nil 指针/切片/映射等反射情形统一归为runtime.None。这正对应文档的表述Typed binders operate onruntime.Valueconstraints and delegate argument conversion to runtime helpers. They do not reflect over arbitrary Go functions.类型化绑定器基于runtime.Value约束工作把参数转换委托给运行时辅助函数不通过反射处理任意 Go 函数。宿主边界转换的保真要求SDK 编解码器在宿主边界做转换时必须完整保留上下文context、可选配置、显式None、未知字段策略unknown-field policy、根类型校验——这些全部按 SDK codec 的配置执行。文档与pkg/sdk包说明一致Decode 选项可以要求根运行时类型、限制带标签的根字段、拒绝未知字段、区分显式None与省略的配置。Runtime 所有权宿主值只接入不重定义模块与 SDK 代码消费运行时值语义而不重新定义它们。宿主值通过运行时持有的接口interface选择加入opt in特定能力相等性equality、排序ordering迭代iteration、查询query分发dispatch、资源resource调试器行为debugger。这些接口定义在pkg/runtime如runtime.Value之上的能力接口层次参见 runtime.md。两个归属原则值得强调仅针对某个 FQL 函数的参数校验留在函数边界附近与 VM、编码、调试器或其他函数共享的语义必须进入pkg/runtime——尤其是哈希/相等性与资源所有权要求参见 Runtime and lifecycle。从pkg/sdk/doc.go可以看到对应的宿主值设计HostValue表示不透明包装器身份只做身份相等而IterableValue、IteratorValue、SliceView、MapView只选择加入它们各自实现的那部分运行时能力。这保证了宿主值不会无意中继承超出其实现的能力语义。标准库不可变能力组与内建注册能力组的构成pkg/stdlib拥有 Ferret 的内建函数、命名空间与不可变能力组选择。从 pkg/stdlib/group.go 可以看到全部 16 个组Group组说明组说明types类型工具datetime日期时间strings字符串arrays数组encoding编码objects对象crypto密码学ioI/O展开为 fs netmath数学fs文件系统random随机net网络collections集合path路径utils工具testing测试其中IO是一个聚合组通过expandGroup展开为FS与NET两个组pkg/stdlib/group.go。不可变 Set 与选择操作stdlib.Set是不可变的能力组选择pkg/stdlib/set.goFull()包含全部组Safe()完整库减去 I/O 组——即不含文件系统与网络适合沙箱环境Empty()空集Only(...groups)仅包含指定组With(...groups)/Without(...groups)基于克隆的增减返回新 Set不修改原值。非法组名会被记录在invalid列表直到Register时才以invalid stdlib group(s): ...报错。Set.Register(ns)只注册选中组对应的register函数到命名空间。// 组合示例只启用类型与数学组 set : stdlib.Only(stdlib.Types, stdlib.Math) if err : set.Register(ns); err ! nil { /* 处理错误 */ }pkg/stdlib/lib.go中的New()直接调用Full().Register(ns)并注册全部函数到根命名空间。内建函数的三条规范保持小巧内建函数应当小边界校验在函数边界校验面向 Ferret 的参数并在错误中保留参数上下文委托共享语义把共享语义委托给运行时辅助函数。文件系统与网络功能通过配置的宿主服务暴露而不是绕过pkg/fs或pkg/net的策略层——这与HostContext.FileSystem()/Network()的设计一脉相承。取消传播规则标准库函数传播上下文遵循 stdlib/README.md 中的规范取消规则。文档明确刻画了取消语义的边界VM 在控制返回后于自己的安全点观察普通执行取消同步 stdlib 工作可能在上下文已取消的情况下完成下游阻塞能力blocking capabilities通过传播的上下文自己拥有取消WAIT拥有自己的 timer/select 处理无上下文的文件系统与熵读取无法被上下文中断crypto token 采样在读取之间不做轮询。各能力组的注册形态一览组规范命名空间兼容形态Collectionscollections::count、count_distinct、includes、reverse已弃用全局别名共享同一实现Strings全局字符串操作—Encoding / Crypto独立命名空间的序列化与加密操作—Pathpath::命名空间—Arraysarrays::不可变、arrays::mut::显式变更冻结的全局迁移别名Objectsobject::不可变、object::mut::显式变更七个临时弃用全局别名DateTimedatetime::弃用全局迁移适配器Mathmath::弃用全局兼容函数其中 Objects 的弃用全局别名通过结构化 API 元数据structured API metadata声明替换方案不产生编译器或运行时警告——弃用在 Ferret 中是 API 元数据概念而非编译告警。stdlib.Full()注册的函数定义同时也是已发布的 Ferret Core API 产物的来源其结构化文档要求与生成校验参见 Core API artifact maintainer guide。Math 函数与兼容性规范命名与数值语义命名迁移Math 组注册规范的math::函数与弃用的全局兼容函数。标量签名与计算共享同一实现规范名替代的全局名math::meanaveragemath::variancevariance_populationmath::stddevstddev_population样本形式如 sample variance/stddev保留后缀。全局注册使用独立的分开声明——因为共享函数会连带共享其弃用元数据。弃用是 API 元数据不是编译器或运行时警告已导出的 Go 入口点保留其兼容契约。集合数学语义规范集合数学接受原生Int与Float元素包括混合列表与非有限浮点其他元素以参数位置 从零计的元素索引报错不做强制转换no coercion。弃用全局保留数值过滤行为。两种策略共享遍历与计算辅助函数。共享遍历使用runtime.List.ForEach传播上下文宿主错误直接返回不带成功部分结果即使取消并发发生操作错误仍直接返回。成功遍历与排序在返回前不检查取消。计数来自遍历而非Length。求和、均值与极值使用常数附加存储方差使用 Welford 单遍递推并除以N或N-1标准差取对应平方根。源不要求支持重复遍历。中位数与百分位数在私有快照中对原生数字排序使用调用方上下文的运行时比较从不复制、排序、索引或变更源。选中值保留其原生类型——即使中位数与插值结果也是 Float。空列表与无数字列表的行为对照文档给出了一张关键的对照表务必完整保留操作规范空列表旧版空列表旧版非空但无数字列表Sum整数0整数0浮点0Mean / averageNaN浮点0浮点0Min, maxNoneNoneNoneMedianNaNNoneNoneVariance, standard deviation, percentileNaNNaNNaN样本统计需要两个数字总体统计对一个有限数字返回零。非有限输入对 variance 与 standard deviation 产生NaN。规范列表若含非数字则失败而不会退化为数值空输入。percentile 与 range 的边界math::percentile(values, p)恰好接受两个参数p必须是0..100内的有限Int或Float即使列表为空也校验线性插值使用升序位置(p / 100) * (N - 1)精确位置保留所选原生值旧版全局保留整数百分位1..100、两/三参数、默认最近秩nearest rank、精确的interpolation方法字符串其他字符串回退到最近秩其数值空快照在百分位校验前返回NaN提供的方法仍必须是字符串。区间生成属于 Arraysarrays::range(start, end[, step])与弃用全局及导出的 Gomath.Range转发器共享实现。全局仍由 Math 组为仅 Math 的嵌入注册不注册math::range。常量、标量新增与 clamp/sign 语义注册表支持函数而非命名空间常量因此pi 以math::pi()暴露附带弃用全局pi()欧拉数以math::e()同样模型暴露无全局别名。仅规范新增的标量clamp、sign、trunc、cbrt、hypot、log1p、expm1接受原生Int与Float参数且不做强制转换。其中clamp(value, min, max)拒绝 NaN 边界要求min max使用运行时比较在低于 min 时选原 min、高于 max 时选原 max、否则选原 value三者间选择结果保留所选 Int/Float 类型与精确表示不做数值转换相等性保留value含符号零允许无穷边界NaN 输入在边界校验后原样返回。sign返回 Int -1、0 或 1两个符号零都视为零接受无穷拒绝 NaN。truncInt 输入原样返回Float 输入用 Gomath.Trunc保留 NaN、无穷与符号零。cbrt/hypot使用 Go 实现包括负值实立方根与避免不必要溢出/下溢的距离计算。log1p/expm1计算ln(1x)与exp(x)-1在接近零处数值精度更高四个函数返回 Float 并保留 Go 的域与非有限行为。均不引入弃用全局函数。COLLECT AGGREGATE 与数学注册的边界内建COLLECT AGGREGATE归约使用VM 持有的语义含泛型终结独立于公开数学注册。只有不带限定符的COUNT、SUM、MIN、MAX、AVERAGE被识别为归约命名空间与其他自定义选择器走普通函数分发因此显式math::选择器保持严格语义。参见 runtime.md 的 VM execution 一节——OpAggregateReduce的AggregateKind与融合收集器共享状态更新/终结逻辑而命名空间选择器仍是普通调用。随机值生成random::命名空间与旧版 rand 的并存规范注册形态独立 Random 组注册固定元数函数random::float()、random::float(min, max)random::int(min, max)random::bool()random::choice(values)、random::shuffle(values)。Full()与Safe()都包含该组没有新增全局别名。仅 Math 的嵌入保留弃用全局rand仅 Random 的嵌入只暴露规范名称。数值区间与算法细节Float 区间[min, max)只接受原生 Int/Float 边界边界必须有限转换前用运行时数值比较排序不等边界时对两端取 ceiling 到可表示 Float 以保留原数值区间无可表示 Float 的区间失败插值避免极值有限端点的溢出并在排除的上界处修正舍入相等边界直接返回min的普通 Float 转换含符号零不抽取。Int 区间[min, max]要求原生 Int 边界通过无符号宽度算术与无偏有界采样覆盖整个 int64 域相等边界直接返回 Int不抽取。布尔抽取消费同一随机源参数校验不消费随机性。choice 与 shuffle 的实现语义choice只调用ForEach单遍水库采样reservoir samplingO(n) 时间、O(1) 附加存储访问计数独立于回调索引从空遍历返回None。shuffle调用源的New(ctx)工厂单遍遍历源把原始值引用追加进独立目标结果保留源的实现族与后端配置原生 Array 输入产生 Array源在操作期间只需要New与ForEach目标提供Append/Length/Swap用于物化与降序 Fisher-Yates负目标长度以ErrInvalidOperation失败。不会把存储型 List 强行物化为内存 Array。两者都借用源与产出值保留值身份源内容不变。遍历拥有迭代器清理工厂拥有失败构造构造成功后 shuffle 在成功前拥有目标——后续任何操作错误含宿主取消都会恰好一次关闭可关闭目标并把清理错误与主失败连接保留源遍历与迭代器清理原因。成功的目标保持打开交由调用方/VM 正常所有权。借用源与元素从不显式关闭。两个算法都使用现有的无偏rnd.Source.Int64原语。choice 对第 2 到第 n 次访问抽取shuffle 仅在成功物化后抽取对应快照位置 n-1 到 1。空与单元素输入不消费随机性。随机源的生命周期默认源在第一次实际抽取时才获取熵从不抽取的查询含无效参数、相等规范边界、空/单元素集合输入不初始化源。显式播种的源在构造时即就绪。pkg/rnd拥有非密码学生成器机制Session 拥有随机源上下文只负责传输。VM 的OpRandWAITFOR 抖动使用同一源。直接 stdlib/VM 集成必须用rnd.WithContext显式提供源缺失源以runtime.ErrUnexpected失败。参见 runtime.md 的 Session randomness 一节——每个普通会话都拥有独立的rnd.Source顺序运行与调试器恢复推进既有序列而非重新播种ferret.WithSessionRandomSeed(seed int64)选择确定性序列。旧版 rand 与安全提醒旧版rand()在[0, 1)抽取rand(x)用minx/2, maxx*2rand(max, min)保持最大优先顺序两个区间形式都保留floor(u*(max-min1))min、宽松的runtime.ToFloat转换与历史的反转/非有限行为每次成功旧版调用抽取一次含相等边界。Go 辅助函数runtime.RandomDefault、Random、Random2仍是弃用的独立包装器各自创建全新源从不被 Session 执行使用。文档特别提醒伪随机值支持自动化与可复现测试不适合密码、认证令牌、密钥或其他安全敏感用途安全随机性保留在 Crypto 组。测试策略模块、SDK 与标准库的验证路径文档给出的测试指引可以归纳为四个层次包测试package tests用于模块注册与钩子顺序验证SDK 公开面 pkg/sdk/sdktest通过其公开接口与黑盒 harness 演练 SDK 作者工作流——sdktest提供基于 Engine 的黑盒测试工具可在 FQL 中执行模块函数FQL 优先只要可行就用 FQL 测试内建函数行为使参数校验、注册、运行时语义与输出一起被覆盖注册测试清单应包括无效定义、重复 arity、大小写不敏感名称、原子失败与生命周期清理宿主值测试应覆盖其实现的每个能力尤其是比较、取消、所有权、转换失败与调试器检查。对 stdlib 注册或结构化 API 文档的变更可能需要运行维护者指南中描述的聚焦生成器测试并考虑 Release automation 中的发布流程。结语分层契约下的扩展模型Ferret 的扩展体系本质是一份契约分层设计pkg/module定义稳定的引擎扩展边界pkg/sdk提供不越权的作者便利层pkg/stdlib以不可变能力组承载内建函数而所有跨层共享的语义统一沉淀在pkg/runtime。对于嵌入方而言这意味着能力裁剪Safe/Only、宿主服务替换文件系统、网络、生命周期观测钩子与函数扩展SDK 注册四条路径彼此正交、互不侵入。对于模块作者而言遵守注册阶段布线、运行时拥有状态、共享语义归 runtime三条原则即可写出与 Ferret 自身标准库同等质量、可长期维护的扩展。相关指南架构总览运行时与生命周期开发工作流发布自动化标准库 README赞分享网页爬虫后端开发工具【免费下载链接】ferretDeclarative data automation language and Go runtime for structured extraction workflows.项目地址https://gitcode.com/gh_mirrors/fe/ferret点击查看免费下载相关推荐A2UI 统一 SDK 架构深度解析Core SDK、Inference SDK 与 Framework Adapter 三层体系A2UI 统一 SDK 架构深度解析Core SDK、Inference SDK 与 Framework Adapter 三层体系 导读 本文以仓库 sdks人工智能AI AgentAI 应用前端UI组件Elsa Core 分层架构深度解析模块化工作流引擎的设计与实践Elsa Core 分层架构深度解析模块化工作流引擎的设计与实践 Elsa Core 是一个面向 .NET 的模块化工作流平台其核心引擎刻意保持轻量将管理后端工作流自动化流程编排低代码PlotJuggler 4 开发者指南模块化架构、构建体系与协作规范深度解析PlotJuggler 4 开发者指南模块化架构、构建体系与协作规范深度解析 PlotJuggler 4以下简称 PJ4是本仓库正在从零重建的新一代桌面时数据可视化桌面应用数据分析上一篇免费PS3模拟器RPCS3从下载到玩上第一局的完整路径下一篇CANN/asc-devkit浮点数转整型API创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考