Perl 原定 E04:Moo 角色不是给对象套一层名字
Moo 的价值不只是 has。属性负责对象状态,角色负责可组合的行为契约;如果写一个空 role 再 with,只是换了语法,没有证明组合边界。课程实验把传统 bless 聚合器和 Moo 聚合器放在同一个 new/add/rows 协议下比较,避免把对象系统演示变成另一份不相干代码。 LogTool::Course::Role::LevelRows 是真实角色:level_from_record 从 hash 或拥有 level 方法的对象取级别,并拒绝非 ERROR|INFO|WARN;sorted_level_rows 把计数 hash 转成按 level 排序的 {level,count} 数组引用。LogTool::Course::MooAggregator 用 with 'LogTool::Course::Role::LevelRows' 组合角色,counts 属性保存状态,add 复用角色校验,rows 复用角色排序。TAP 断言该类 does 这个角色,并与传统 LogTool::Course::Aggregator 的输出逐项相同。 123456package...
Perl 原定 E03:SQLite 事务和绑定参数要一起验证
DBI 把连接、预处理、绑定参数和事务边界分成不同层。DBI->connect('dbi:SQLite:dbname=:memory:', ...) 得到数据库句柄;begin_work 关闭自动提交直到 commit 或 rollback;? 占位符由驱动绑定值,不把字符串拼进 SQL。只证明 SELECT 1 能跑,不足以覆盖课程里的工程用法。 累计实验 examples/perl-language/experiments/course-sqlite.pl 创建内存表 events(level, message)。第一段事务插入 INFO/kept 后 commit,第二段插入 ERROR/rollback 后 rollback;最后 SELECT COUNT(*) 只剩一行,按绑定参数查询 level = ? 返回 kept。t/course-dependencies.t 同样断言回滚行消失、参数化查询返回预期计数。 123456my $payload = q{x' OR 1=1 --};$dbh->do('INSERT ...
Perl 原定 E02:Text::CSV_XS 要实测多行字段与转义边界
CSV 的难点不在逗号,而在记录边界和引号。Text::CSV_XS 的 binary => 1 允许字段中出现换行等字节;双引号字段内部的 "" 表示一个真实的 " 字符;未闭合引号必须被拒绝。课程若只用 join ',' 或只测 a,b,无法证明报表能处理接收方真正会遇到的数据。 累计实验 examples/perl-language/experiments/course-csv-xs.pl 固定加载 Text::CSV_XS 1.64,解析 id,"two\nlines","a""b"。测试断言字段结果是 id、带真实换行的 two\nlines、以及 a"b,同时把 id,"unterminated 判为 rejected。t/course-dependencies.t 又在同一数据上运行 Text::CSV_PP,要求字段结果一致,但后端身份不同。XS 和 PP 的行为可以比较,不能互相代验。 1234use Text::CSV_XS ();my $csv...
Perl 原定 E01:单行脚本要能还原成完整脚本
perl -n、perl -p、perl -a 常被写成命令行技巧,但课程验收不能只看屏幕上几行输出。Perl 5.38.2 的 perlrun 说明里,-n 给程序包一层 while (<>) { ... },不会自动打印;-p 同样包循环,但每轮末尾打印当前 $_;-a 在隐式循环里把 split 结果放进 @F,并且文档明说 -a 会隐含设置 -n,-F 会隐含设置 -a 和 -n。这些开关改变的是解释器包裹源码的方式,不是另一套迷你语言。 累计实验 examples/perl-language/experiments/course-oneliners.pl 先写入三行 TSV,再运行三条单行脚本:-ane 'print if $F[0] eq "INFO"' 过滤 INFO,-pe 's/^WARN/ERROR/' 生成替换后的流,-F'\t' -lane 'print "$F[0]:$F[1]"' 输出字段对。实验还把第一条单行脚本还原成完整脚本:显式 while (<>)、显式 split、条件 prin...
Kotlin E01:Compose 离开页面后 Flow 还会收集吗
31 已经验证冷流每次收集会重新执行上游,32 则区分状态与事件。把进度显示搬进 Android 界面后,还多出一个问题:用户按 Home,界面不再可见,收集者是否继续工作?如果旋转屏幕,按钮计数和进度又会怎样?这两个问题不能只靠 APK 编译结果回答。 一个界面里其实有两种状态 隔离工程 examples/kotlin-language/android-compose/ 的 TaskActivity 只做一个可观察的练习。按钮计数由界面本地维护;冷 flow 每秒发出一个递增的 tick,collectAsStateWithLifecycle 把它转换为 Compose 可读取的值: 12345678910111213141516var clicks by rememberSaveable { mutableIntStateOf(0) }val progress = remember { flow { var tick = 0 try { while (true) ...
如何画和理解轨道图:以 Elasticsearch Query DSL 为例
《查询与检索》里有一张像铁路线路的图:从 start 出发,经过分叉和回路(loop),最后抵达 end。它不是 Elasticsearch 的执行流程图,也不是网络拓扑图,而是一张轨道语法图(railroad diagram)。 轨道图只回答一个问题:一段结构化输入有哪些合法的排列方式? 以 Query DSL 为例,图中可以同时表达请求体的顶层参数、query 的值以及 bool 的嵌套方式。读图就是沿着轨道走;画图就是把语法规则翻译成路径。 这里的“节点是语法元素”需要再分细一点。query、sort、{ 和 , 是终结符,最终会出现在输入中;QueryClause、SortValue 和 Number 是非终结符,只表示还要继续展开的语法类别。标准铁路图用两侧边界标记起点与终点。借用 Mermaid flowchart 画关系图时,可以增加“引用”之类的连线标签,但它们不属于实际输入。 EBNF 是 Extended Backus–Naur Form 的缩写,中文通常译为“扩展巴科斯范式”。这里的 form 是“形式、记法”的意思,不是另外一个隐藏缩写;Back...
Go E08:双实例租约与重试为何不等于恰好一次
E07的独立消费者可以向累计任务服务提交任务;但原服务在单实例重启时把残留 RUNNING 标为 FAILED,这不等于另一个实例可以安全接手。为把差异变成可复跑的实验,本篇在同一 go_tasks 表与原统计函数旁增加显式的 taskstats lease 教学入口,由独立子进程 A/B 领取同一任务。普通 serve 的恢复策略不变,也不能与该实验模式混跑。源码、原始命令/双流和 PostgreSQL 查询在完整素材包、SHA-256和独立复跑说明中。 RUNNING 需要归属与期限,不能只看状态名 单实例原有 Claim 的 QUEUED → RUNNING 条件更新避免同时领取,却没有记录哪一个进程应完成任务。租约模式在原任务行增加 lease_owner、lease_epoch 与数据库时钟下的 lease_until。领取是一条带条件的 SQL UPDATE RETURNING:只有 QUEUED 或租约已过期的 RUNNING 才能被写成当前 worker 的所有权,并把 epoch 加一。两个连接同时争同一条新任务,十轮 SQL 测试每轮只有一个成功;这是一组已执...
Go E07:workspace 与多 module 能否隐藏依赖
E06在同一个累计任务服务上比较了编译配置。若外部工具也要提交并查询任务,是否应该让它直接导入服务的 main 包?不能:服务包封装了执行器、SQL 和诊断端点,消费者只需要稳定的 HTTP 请求/响应契约。本篇从累计工程中拆出 client/:只实现 Submit、Lookup、Cancel 的薄客户端。它不接触任务状态机,更不会复制 worker。代码、原始双流和真实数据库实验见完整素材包、SHA-256及独立复跑说明。 一个成功的 workspace 构建证明不了依赖闭合 冻结 Go 1.27.1。go help work 规定 go.work 的 use 把列出的 module 当作工作区根模块,并允许工作区替换覆盖模块内的替换。累计工程的 go.work 列出原服务、client/ 和 consumer/;后者声明 require example.com/go-language-task/client v0.1.0,却没有独立的解析途径。GOWORK=auto 从 consumer/ 构建通过,只说明这三份源码在同一个目录树中能协同编译,不说明独立消费者能拿到这个版本。...
Go E06:PGO 在真实负载下有无收益
E05只换了 HTTP 路由器。现在回到原任务服务的实际负载:同一个 Go 1.27.1 工具链,给编译器提供一份代表性 CPU profile,构建 PGO 开关两版程序,然后让真实进程完成同一批任务。完整源码和原始结果在素材包,另有SHA-256及独立复跑说明。 profile 是输入,不是收益证明 下列 profile 与耗时数字来自初版 Linux 实验,原始输出保留在包内 evidence/E06/。2026-10-12 的 Darwin/arm64 补齐复跑使用同一 Go 1.27.1 源码、实际训练 profile 和运行前记录摘要的两版二进制:训练同形负载 off/on 中位 106.838/112.170ms,比值 1.050;留出负载 102.627/113.742ms,比值 1.108,两组均观察到退化。新原始记录位于仓库 writing-plans/go-language/completion/evidence/pgo/;不同平台和轮次的绝对耗时不能互相替换。 固定 BenchmarkTaskAnalyzeContext 已有输入 "Go one...
Kotlin E08:if 表达式怎样走过 FIR 与 JVM 后端
suspend 函数怎样变成可恢复的字节码 固定 Kotlin 编译器源码并对照了 `suspend` 的 JVM 生成物。它重点解释后端为什么需要额外的 continuation、标签与状态字段;想区分前端与后端,不必再次从协程下手。一个普通的可空字段判断,足以展示“类型被分析”“条件被表示”“分支成为 JVM 指令”是三种不同的工作。 选一条已经在工程里运行的表达式 累计工程的 CommandParser.kt 没有为编译器研究改写业务语义: 1234fun textLength(source: TaskTextSource): Int { val captured = source.text return if (captured != null) captured.length else 0} TaskTextSource.text 是自定义 getter。读入局部 val captured 再判断非空,避免在判断后重新调用一个可能返回别的值的 getter。新增 CompilerPhaseBoundaryTest 将 getter 的调用...










