MoreRSS

site iconEltrac | 極客死亡計劃修改

常讨论心理学和社会观察相关内容,不敢说很懂哲学;也会写笔记和生产力工具相关内容;还是学生。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Eltrac | 極客死亡計劃的 RSS 预览

论遣词造句

2026-02-17 18:52:20

语言被互联网文化变成了什么样子,在此不必赘述。要求普罗大众都精确用词、尊重文字,其实不太合适,因为即便言之凿凿,也避免不了说教味。本文并不是规范、要求或提倡,只是在规范我自己的文字表达,以及给出一个我眼中的好的遣词造句的标准。我也不纠结文风(相反我十分鄙夷「文采」这一说法),但我在意我写下的词句是否准确和得体,更重要地,能否使人读懂。

如果你也赞同我的观点,并打算和我以相似的态度规范自身,我会感到十分欢喜。即便观点不能做到完全契合,我也会将这种差异当作交流的机会。

个性和风格与用词有关系吗?

首先要建立起的共识是,用词准确不代表写出呆板、千篇一律、毫无变化的文字。语言的多样性,想必不只是通过用词体现出来的,在用词之上,还有修辞。再深入一些,使用语言的目的是表达想法,而只要想法是多元的,语言也不会同质化得让人觉得无趣。反观,如果一切个性都必须通过哗众取宠的词汇体现出来,恰恰说明了这个人缺少有趣的灵魂。

我说的「哗众取宠」的词汇指什么呢?说实话,只需要到一个人员组成足够复杂的简中社交平台上看一看,就能找到大把大把的例子。对网络热词的鄙夷也是陈词滥调了,在此也不多提。其实,我并不反感黑话,前提是在小圈子里说给能听懂的人听,但是在人员复杂的公开平台上,让背景不同、兴趣不同、性格不同的人说同样的话,就显得有些可怕了。在我看来,人们在社交平台上使用此类词汇,一方面是下意识地谄媚,另一方面是为了感到安全。总想说点好玩的话来取悦一整个互联网(或者所有路过某个讨论区的人),想想就不太健康。为了保证失败的概率最低,人们使用大家都喜欢说、都「同意」使用的互联网黑话,牺牲个性和真实的思考,来获得表达的安全感。倘若仍然不可避免地失败了,就将对方定义为好事之徒,以便自洽。

我跑题了,本文要谈的并不是无法改变的有毒的互联网环境。我观察到,词汇的重复从大的社交平台一直蔓延到了小互联网、文字博客,甚至现实生活。那些愿意留在鱼龙混杂的社交平台上自取其辱的人我并不在乎,但这种风气以某种形式传染到了其他地方,就有点难以接受了。

回到这一小节的标题上来,个性和风格与用词当然是有关系的,但并不代表好好说话就会让自己失去个人风格。在我看来,大多数时候,写作者无需在意风格问题,也无需纠结某段文字是否足够「像自己」,借用 Lawrence Block 的说法就是:

形成个人风格的最佳手段是尽力自然而诚恳地写作。构成个人风格的不是刻意为之的文风,不是充满诗意的段落,也不是对文字结构的疯狂追求。写作者无需刻意为之,风格就会自然呈现。

——《 小说的八百万种写法

因此,完全不必把互联网上乌烟瘴气的那一部分带到写作中来,更不应该带到现实中去。

尽管只要诚恳地写作,就无须在意风格问题。所谓的「诚恳」,除了坦诚地表达想法、适当地使用修辞,当然还包括准确地用词,因为诚恳的写作者当然希望读者能够读懂自己写的东西,若用词不精确,这个目的就难以达成。

什么是精确且诚恳的表达?

不得不提的是,我观察到人们总是高估其他人和自己的相同点,以为用自己习惯的、见得比较多的表达,其他人就都能听懂。有个人曾经跟我聊天的时候,表达了他对我的不满,他觉得我在跟他抬杠,因为我一直在问他「这个词是什么意思?」,他觉得人人都应该理解这个意思,就算不知道,也能从字面意思上推断。天哪,我的严谨态度竟然变成了傲慢!我只是不想误解他,难道我要装作听懂了自己没听懂的东西,才算是尊重他吗?!

我必须反驳这种观点,在面对不熟悉的概念时,仅仅从「字面意思上推断」是非常不负责任的行为,继而导致了不少词汇的误用。这种不负责任随处可见,人们不仅高估其他人与自己的共性,还高估自己的理解能力、高估自己的经验。我非常憎恶的是,人们觉得「XXX 是个伪命题」这一表达是正确的,并且广泛地接受了这个错误的用法,甚至在一些官方媒体上都能见到,因为他们从字面意义上推断「伪命题」的含义。查查字典吧!「命题」指的是能被判断真假的想法和论述,而「伪命题」就是假的命题。然而,人们却把「伪」理解成了「伪装」之类的意思,把「命题」模糊为「概念」的同义词,把「伪命题」当作「不存在的概念」「不应有的想法」甚至「骗人的说法」,继而有了「无糖饮料是个伪命题」这种被堂而皇之接受了的病句。

总而言之,要做到精确且诚恳,要避免两件事情:一,高估他人与自己的共性;二,高估自己的理解能力。

我更偏爱长文而非短文本,原因就在于,长文本可以在行文过程中逐渐建立起共识,如果有人一开始就不同意文中建立起的共识,那这位读者自然知道,后面的内容也不会让他感到满意。人们之所以容易在社交平台上爆发难以调和的争吵(而且很少能吵到点上),原因之一就是共识的缺乏,当然还有前提和论述的缺乏。社交平台适合记录和分享,不适合进行智识交流。依赖相互理解和清晰思考的智识交流需要建立共识。

撰写长文,建立共识的过程,就是认清自己与读者有相当多的差异的过程。正是因为有差异,才要在写作时把一切前提都交代清楚,把讨论的基础打好,以便促进理解。这个态度落实到遣词造句上,就是使用最基本的词句表达想法,遇到某些概念时,不吝啬笔墨解释概念。

遇到某些特定的概念和词汇,尤其是专有名词,应当查阅可靠的资料了解词汇的意思,而不是从字面意思上推断,然后就拿来用了。更不应该因为经常听到人们使用某个词,就假定这个词真的存在。比如,许多人都以为有「拉丁语系」这个词,事实并非如此,从严格的语言学分类来看,语系是相当大的分类,一般有「印欧语系」「汉藏语系」等,而印欧语系下,有「意大利语族」,这个语族的最大分支名为「罗曼语族」,罗曼指罗马(Roman),这个语族下都是由通俗拉丁语演变而来的语言。显然,「罗曼语族」才是正确的词,而「拉丁语系」则完全不存在。

有人可能会反驳:“我又不是语言学者,为什么要了解这么复杂的分类和术语?语言是用来交流的,只要对方能听懂就好了!”我会说:既然不是语言学者,也对语言学没有研究,那就不要乱用语言学的词汇,你完全可以不使用「语系」这个专有名词,直接说「由拉丁语演变过来的语言」,听话人反而更容易听懂。

高估自己的理解能力和经验,就会犯这样的错误。使用最基本的词汇,尽可能避免不熟悉的专有名词,才有助于相互理解。简洁不是简短,清晰比聪明更好!1这句编程领域的谚语,同样可以用在自然语言上。

避免欧化的中文

我不是在谈什么文化自信,我只相信:中文应该像中文。任何语言都是这样,这也是为什么充满了英语舶来词(还全是用片假名写就的,甚至不如直接写英文)的日语这么令人头大。中文欧化的说法几十年前就有,如今看来问题非但没有好转,反而变得更常见,这里主要分四点来谈。

1. 无处不在的量词和代词

英语里,单数不可数名词前必须有冠词是语法规定,所以 a、an、the 十分常见,指示代词 this 和 that 的出现频率比中文的「这个」「那个」更高。法语里,定冠词 le、la、les 用得也比英语里的 the 更多。在翻译的时候,难免译出很多「一个」「那个」「这种」,然而,很多时候这些词都是累赘,可以删去,不影响表达。

比如,「这是一种非常不负责任的行为」,完全可以写作「这是非常不负责任的行为」,除非是为了强调,也可以更进一步地写成「这非常不负责任」。

这个错误我经常犯,写着写着就出现好多量词,我也经常使用「这种」「这些」。可能是口癖,如非必要,量词和代词用多了非但不会让表达更精准,还会让句子显得冗杂,降低表意效率。西文里这么用没问题,是因为那些语言的冠词里基本上只有一个音节,而且是非常短的音节,说话时很容易和其他音素连接起来;书写时,由于只有两三个字母,单词之间还有空格,也很容易忽略。可中文就不一样了,上述量词和冠词几乎都是两个音节,而且汉语发音字正腔圆,很难忽视;汉字在书写和排印时,中间没有空格,也不容易跳过。

此外,除了表意效率低下,重复也是令人乏味的。如果非要用「这些」,为什么不可以换成「上述」「此类」和别的词呢?

2. 抽象名词和欧化表达的泛滥

这个问题是否是欧洲语言的影响,貌似有争议,因为也有西方学者表示,滥用抽象名词的英文写法实际上是…… 中式英语。

关于这个问题,我不太能确定自己能否讲清楚。说到底,我的中文也算不上好,缺乏精炼有力的表达,甚至连白话都算不上。去读读鲁迅先生的文字,再随便找本译著进行对比,应该能发现明显的区别。不只是用词,遣词造句的结构都有质的差异。

既然我自己讲不清楚,读者读到这里多半也疲乏了,那么我就偷个懒,放两期我认为讲得不错的视频在这里。它们都出自「迷幻枸杞」这位视频博主,你可以在 Bilibili 上观看;如果你在乎隐私的话,可以使用 Invidious(YouTube 的第三方隐私前端)来观看,这里链接的是 yewtu.be ,Invidious 的公共实例。

  • 《真相大白:如何根治中式英语?》 Bilibili / yewtu.be
  • 《‘英文没学好,中文却学坏了’ :欧化汉语是什么》 Bilibili / yewtu.be

3. 多语言混杂

如果要证明自己的语言能力,比起在中文里插入英文,不如撰写全英文的文章,或者发表英文演讲。同理,写中文时就应该好好写中文。理想情况下,应该只同时使用一门语言。中夹英完全不利于促成相互理解,不会英语的中文使用者觉得疑惑,有素养的双语者觉得讨厌。总之是不恰当的做法。

比较难解决的问题是,如何处理那些难以翻译的英文单词,尤其是专有名词?

这在软件领域尤为常见。从我有限的经验来看,我在撰写散文和人文社科相关的文章时,基本上能做到只用中文。然而,在撰写技术相关的文章时,我不得不在中文里夹杂英文,比如 JavaScript、Cloudflare、Docker 等等。很多软件、编程语言和技术,都没有广泛接受的中文名。此外,品牌和产品名也是如此,就算可以用「苹果耳机」代指 AirPods,两者给人的印象也是完全不同的。或许可以用苹果电脑代指 MacBook,但如果是更细分的产品线呢?MacBook Pro 难道要写成「苹果电脑专业版」吗?就算大众能接受,品牌方也是不会这样宣传的,中国的科技品牌都很少用中文给品牌和软件起名字。

把 Cloudflare 写成「火烧云」,把 ActivityPub 说成「活动酒馆」,把 Webmention 说成「万维网提及」,把 WordPress 说成「文字报社」,在社交时使用很有趣;如果受众是熟悉此类概念的读者,也是不错的幽默,算修辞;可其他时候使用就不合适了。首先,大部分人都不熟悉这些中文译名,让人疑惑。此外,这些名字都有歧义,使用原名才能准确表达,而这正是我们追求的精准用词。

所以,重点不是使用什么语言、要不要在文字里夹在另一门语言,而是要考虑怎样才能做到准确且诚恳。

4. 乱用音译

为什么要把 Cheese 译作「芝士」,是忘记有「奶酪」这个词了吗?真要追求高级感,为什么要用烂大街的英语?用法语吧,译成「弗洛麻吉」(fromage),前半部分像个奥地利心理学家的名字,后半部分像个日本动漫角色的名字,可谓是一举三得。

Cheese 就是「奶酪」,中文完全有能力不借用英语读音,直接用中文里的词表达 Cheese 的意思,不需要译成「芝士」或「起司」。中国有些地区惯用英语,使用音译词还能理解,但这种说法传播过来,就用欧化中文之嫌。

关于中文欧化的问题,还可以读读思果写的《 翻译研究 》。尽管书本的主要内容是英译中的方法论(比如「Yes」在不同的语境下译成中文,有十多种译法),但也有不少关于「怎么写中文」的内容,毕竟翻译要求的是对两门语言的熟练掌握。

最后

日常表达里的累赘、不自然和堂而皇之犯下的错误,并不罕见。由于几乎人人都会使用语言,错误的语言用例被广泛传播开来也是正常的,没法避免。从词典和标准的修订也能看出知识分子对大众的妥协,比如「坐骑」中「骑」的读音就从 jì 改为了 qí2,大概是因为读错的人太多了。

互联网的不良用词习惯,大概也值得抨击,但总归没办法要求大众准确用词。最近在读塞涅卡的书信,其中有不少文本都与「哲学与大众的关系」有关,他的观点大概是:既不能与大众走得太远,什么都要反着来;也不能同流合污,面对大众的恶习,要退回到自身当中。

我在《 关于肥胖的杂谈 》中写过,在公共层面,不对肥胖群体做出恶意的揣测和批评,在个人层面,追求健康的身体。这可以总结为「严于律己,宽于待人」。我想,遣词造句的自律也是如此。大众可以随意玩弄语言,但追求智慧的人对待语言应该有自己的看法。

我不是说我在文中提到的标准就是正确的,在我看来,重点在于「开始关注这个问题」,也就是对遣词造句有自己的思考,而不是使用他人都在使用的语言。

为此,我打算发起一个 Slash Page 3,名为 /glossary(词汇表)——站长可以在这里写下对用词的思考,描述用语习惯,列出常用词或自己生造的概念,比如「我不喜欢用 AI 这个指代广泛的名词,更喜欢用 LLM(大语言模型)和文生图模型等精确的分类」。撰写这个页面能帮助自己审视语言使用习惯,也能让其他人看到自己的思考。

本站的 /glossary 页面将会在未来几天发布。如果你喜欢这个概念,也欢迎给你自己的网站添加,写完之后记得告诉我!

就这样,回见!


  1. 「清晰比聪明更好」的原文是「Clear is better than clever」。这是 Go 语言创始人之一 Rob Pike 的名言之一,也是 Go 语言的设计哲学。 ↩︎

  2. 1985 年颁布的《 普通话异读词审音表 》规定「骑」字统读为 qí,不再是多音字。不过,台湾和马来西亚貌似还保留了 zuò jì(坐骑)的读法,我用 RIME 输入法也能用 zoji 打出这个词。顺带一提,我之前以为「说服」的正统读音应该是 shuì fú,也经历了修订,查阅后发现并非如此,这个词在中国大陆一直读 shuō fú。 ↩︎

  3. Slash Page 指类似于 /about/now 的网站页面,位于网站根目录,是个人化的、有特殊意义和用途的页面。 ↩︎

稻草人周刊 Vol.68

2026-02-16 00:18:31

Beat Yourself Up music cover

Beat Yourself Up

Charlie Puth

我最喜欢的创作型男歌手 Charlie Puth 释出了新专辑《Whatever’s Clever》中的两首新歌,其中我最喜欢的是这首《Beat Yourself Up》。某天晚上出门散步的时候还在 Apple Music 上发现了他的一期 Radio Takeover1,听到了一些不错的音乐,貌似还被上了一堂音乐课。


这期周刊发布的时候,应该正好是除夕。虽然应该不会有太多人在春节假期还抽出时间来读我的博客,但我还是想给读者说声新年好。此外,我还在上周重写了 关于 页面,这可能是我每年都会做一次的事情。我记得我之前有一段时间,经常因为想到自己放在万维网上的某个页面没办法准确描述自己而坐立不安,频繁删改。既然这样,就干脆把它变成一件周期性的事情吧,也算是定期审视自己了。

如果你观察导航栏的话,会发现不只是关于页面有了变化,剩下的就等读者自己去发现吧。

顺带一提,我的 新个人主页 基本完工了,用 Clojure 编写,源代码在 。我会时不时在那里修修改改,写一点有关自己的东西,而极客死亡计划是我进行思考的地方。所以你可能会在我的主页上找到一些不太得体的内容。

就这样,继续阅读周刊吧!


连接

电子游戏中的道路艺术

📜

电子游戏中的道路,有很多是用贝兹曲线(Bezier Spline)实现的,大部分时候这种纯粹通过简单数学计算制作出来的道路并没有太多问题,可一旦角度太大,就会出现自相交等问题,总之现实中车辆行驶的轨迹不可能是那样的。这种问题在大量的教程和游戏 Demo 中都能发现,还有很多十字路口的转角处竟然是直角,稍微思考一下就能意识到车辆根本没办法在这种地方转弯。

作者开发了他自己的道路系统,这篇文章只是一个引子,更多内容会在之后的文章发布。

作者表示他喜欢那种看起来混乱,但实际上错综复杂且有秩序的存在,现实中的道路就是一个。如果某一天外星人发现了遗留在地球上的道路系统,一定会感到大为震撼:有人花了很多心思设计这个呢!我想起了我之前在 Minecraft 服务器里当镇长的经历,社区发展起来之后,有很大一部分的工作就是修路。除了用道路造景,每个 Minecraft 玩家都知道,下界的冰道交通系统对大型服务器来说是必不可少的,除非玩的是那种有快速传送方式的无聊服务器。我在高中选了地理这一门科目,依稀记得其中有不少知识点都和交通相关,比如交通对商业的影响。

道路真的是人类文明里很神奇的一部分呢。

给别人看 AI 生成的内容是不礼貌的

📜

一本名叫 Blindsight 的科幻小说中,有一种名为 Scrambler2 的外星生物。这种外星生物认为信息的唯一作用就是用来感知,所以无法忍受人类整天说闲话,于是就跟地球开战了。作者表示他现在理解了 Scrambler 们的心情,他在读到明显是 AI 生成的内容时就会感到愤怒。

由于 LLM 只会「回复」,而不会像小说里的人类一样主动向外太空发送无用信息,所以,只要一开始人们不主动传播 AI 生成的垃圾,就不会受其困扰。我们的敌人是人类,而不是 AI。作者认为人们应该学学「AI 礼仪」,使用 AI 是没问题的,但这样是有问题的:

我问了 ChatGPT,这是他的回复:<…>

我用 15 分钟生成了这些代码提交 PR,你审查吧。

简单来说,如果你已经把你从 LLM 那里得到的东西内化于心了,并且能给出你发这些东西的原因、你的想法,那就没问题。

最近在联邦宇宙上看到,有人在发布和 LLM 的对话节选时会折叠内容(标记为敏感内容),只要其他人不主动点开,就不会读到 LLM 生成的内容。我认为这是礼貌的体现,建议所有人学习。

PGP 有什么问题?

📜
📜

PGP(Pretty Good Privacy)是诞生于上世纪九十年代的加密软件,可以用来给文本、邮件、文件和磁盘分区等签名、加密和解密。PGP 本来是个商业软件,后来出现了 OpenPGP 这个开放的标准,GNU Privacy Guard(俗称的 GPG)和 Sequoia PGP 等软件实现了这个标准。3不过,使用 OpenPGP 基本上是绕不过 GPG 的,就像使用联邦宇宙也绕不开 Mastodon 一样,使用率最高的软件几乎可以决定事实标准。上面的两篇文章观点一致,PGP、OpenPGP、GPG 等所有相关的软件,全都有不可忽视的缺陷,所有人都不应该继续使用它。

Latacora 的《The PGP Problem》这篇文章发表于 2019 年,第二篇文章由 Soatok 撰写,撰写的原因是:Latacora 那篇文章发布都五年了,你们这些人怎么还挤破了头去用 PGP?

Soatok 表示,对于密码学专家来说,「PGP 很难用并且有缺陷」这件事并没有争议,但其它领域的工程师似乎并不了解。我设想了一下,如果 Soatok 所言属实,那密码学专家们看到如今还有这么多人用 PGP 的心情,大概就和我看到有人用廉价陶瓷磨豆机磨瑰夏,还不控制水温一通乱冲一样。

简单来说,PGP 是把瑞士军刀,好处是什么都能干,坏处是什么都干不好。Latacora 那篇文章中反复强调的观点是:兼容性和加密的安全性,是不可兼得的,而 PGP 一直在保证兼容性,保留旧的、有缺陷的加密算法。作者还表示,人们之所以离不开 PGP,很大一部分原因是 PGP 的社区太活跃了,人们热衷于构建信任网(web of trust),互相给密钥签名,甚至会组织线下的签名聚会。此外,文中还有…… 更尖锐的观点:

We can’t accept bad cryptosystems just to make Unix nerds feel better about their toys.

我们不能只是为了让 Unix 书呆子们对它们的玩具感觉良好就接受差劲的加密系统。

要解决瑞士军刀「样样都会但是每样都干不好」的问题,方法当然是给特定的用途找正经的工具来用,比如用 Magic Wormhole 加密传输文件(或者 ssh + rsync 也行)、用 SSH 密钥给 Git commit 签名、用 Sigstore 给软件包签名、用 Signal 进行加密通信而不是用 PGP 加密电子邮件。4 不过,比起常规的加密和签名,PGP 实际上还提供了一个密码学意义上的、去中心化的身份认证系统,这点似乎还没有特定的软件或标准可以替代。

总而言之,PGP 已经是老古董了,近年来给它打上的新补丁看起来也无济于事,有不少缺陷。最好在特定的用途下使用更现代的软件替代,而不是用一个 PGP 密钥对走天下。仔细想想,我一开始接触到 PGP 和 GPG 也只是为了给 Git commit 签名,而这点完全可以用 SSH 密钥代替。

电子邮件是修不好的破车

📜

电子邮件也是老古董了,目前人们仍然是用上世纪八十年代(四十多年前)的 SMTP 协议传输邮件,这个协议使用明文传输邮件内容,很多邮件服务提供商并不会强制使用 TLS(传输层安全协议),这导致电子邮件通信其实并不安全。

书接上文,PGP 可以用来给邮件加密,但 Latacora 的那篇文章、Soatok 和我正在使用的邮件提供商 Migadu 都认为:你不应该给电子邮件加密。Migadu 的说法是,这不符合 SMTP 协议的标准,而且邮件加密之后就没办法扫描垃圾邮件、病毒、给邮件添加索引了,如果密码丢失,你甚至没办法恢复消息;再者,管理密钥也是个问题,邮件服务商肯定是要留一把私钥的。

如果只是发信人和收信人之间自己使用 PGP 加密,也不能完全保证安全。抛开 PGP 本身的缺陷不谈,如果邮件交流的某一方在某次回复时忘记了加密,而过往邮件记录的引文也都在这封邮件里,那之前的加密不就白费了吗?不是所有人都会给邮件加密,就算你发送了一封加密的邮件给某人,而他回复的时候却没有加密,那你加密过的内容仍然会以明文的形式进入到互联网中。

包括我自己在内的许多人都有发现,最近配置域名邮箱所需要添加的 DNS 记录比之前多了好多,那是因为电子邮件一直在加各种安全补丁。Soatok 的文章中谈到了 DKIM,大概是一种从密码学意义上验证某一封邮件是由某人通过某服务器发送的手段,这对防止滥用和诈骗来说很重要,但对于加密通信来说并不理想。电子邮件有泄露元信息(metadata)的问题,就算邮件正文是加密的,邮件的主题、发送方、接收方等信息也难以隐藏。

比起技术层面的各种问题,电子邮件没办法做到完美加密的最重要的原因其实是:政治!

Repeat after me: all technical problems of sufficient scope or impact are actually political problems first.

跟我念:所有有着足够大规模或影响的技术问题,一开始都是政治问题。

—— Eleanor Saitta

首先是大部分人其实并不在乎加密通信和隐私,我相信阅读这期周刊的不少读者都会有这样的想法:「我没有什么好隐藏的。」「加密通信不是罪犯才需要的东西吗?」以至于在乎电子邮件的隐私问题的人很少。也有人设计过更安全、泄露更少元信息的电子邮件通信协议,以取代 SMTP(如 SMIMP ),但并没有取得成果,主要的原因是:电子邮件尽管是开放协议,但仍然是被大科技公司垄断的;他们不在意,就很难做出改变。

Well, that’s pretty fucked up.

读完这篇文章,我想比起想办法让老古董电子邮件更安全,不如使用本身就足够安全的新通信协议。同时,要接受电子邮件并不是「信封」,而是在互联网中传送的「明信片」,不应该指望它有多么安全。

国家应该立法禁止青少年使用社交媒体吗?

📻

近期澳大利亚正式推行了青少年社交媒体禁令,十六岁以下的儿童不能使用社交媒体。56这一举动也引起许多国家效仿,因为他们发现社交媒体禁令在实操层面是可行的,澳大利亚在推行禁令的时间里几乎没有误封成年人的账号。至于为什么要禁止青少年使用社交媒体,主要的影响来自社会心理学家乔纳森·海特的《焦虑的一代》,这本书从教育和技术等角度解释了智能手机和社交平台如何塑造童年,以及如何对青少年造成了伤害。青少年中普遍存在的心理问题其实并非是社交媒体单方面导致的,还有,比方说,鼓励式教育。童年中挫折的缺失,使得青少年在长大后无法面对真实世界——当我听到美国的大学甚至要求老师为课堂内容设置内容警告(trigger warning)的时候,我真的笑出声了。

联系到 上一期周刊 提到的《独树不成林》第 299 期播客中的观点,互联网上争吵不断、社交媒体越来越烂的根本原因是人们无法在互联网上建立共识,而受到不健全教育摧残的青少年又开始在社交媒体上要求网友设置内容警告、照顾自己的情绪,无疑造就了一个更加乌烟瘴气的互联网。

大多数人不会反对的一个观点是:过度使用互联网对任何人都是有伤害的,无论年龄。只不过,人们普遍认为,可以允许成年人伤害自己。那么,要不要禁止青少年使用社交媒体这个问题,就被抽象成了:要不要禁止青少年伤害自己?童年应不应该被保护?

还有另一个很微妙的阶级问题,《焦虑的一代》这本书最有影响力的地方,其实是富人阶层,原因在于富人的小孩子有除互联网以外的、让童年丰富和充实起来的选择——这是巨大的特权。光把手机从小孩身上收走是不够的,家长还应该为小孩提供其他的活动,或者说,更好的方法从来就不是禁止使用社交媒体,而是提供比社交媒体更有吸引力的替代方案。

在面对这样一个教育问题时,最应该关注的不是对儿童的法律约束,而是成年人的意识问题:成年人愿不愿意承担起教育子女应有的责任?又及,我想要扩展一下:在不结婚生子就是异类的社会中,人们真的在意教育问题吗?

最后,原谅我,我还想谈谈年龄验证带来的隐私问题。要验证一个用户的真实年龄,必然要收集用户的身份信息,这可不是邮箱地址和手机号这么简单,要验证年龄,必然要出示有效身份证明。Discord 最近自发地进行年龄验证,他们收集到的数据是怎么处理的呢?毕竟,数据泄露这种事情,已经不新鲜了。年龄验证不会直接创造出更好的童年,也更不会带来更好的互联网,反而会造就新的问题。7

Foo 的词源

📃

看到 RFC(Request For Comment)就会以为这是什么严肃的互联网技术标准,就算是研究词源,那应该也是偏严肃的吧——我在 Hacker News 上看到这篇文章时就是这么想的,直到我在里面读到了「Foomobile」这个词。

抬头,看见日期:1 April 2001

好,好,好。在 2 月份把一篇二十多年前的愚人节文章发到 Hacker News 上的人到底是谁!不过,虽然是一篇幽默的文章,但内容还是有考据的,不是胡编乱造,串联起了各种有关 Foo 词源的说法,以及一些漫画里对 Foo 这个词的使用,奇妙的是,竟然还有说法是 Foo 实际上是中国汉字「福」。

如果你不知道 Foo 是什么:这个词常常出现在各种编程教程或文档的示例代码中,用作没有实意的符号名,比如 ClojureDocs 里就有着这样的例子 (defn foo [a b c] (* a b c)),使用 Foo 来演示如何在 Clojure 中定义函数。

星群

RustCast

顾名思义,这是一个用 Rust 写的 RayCast 开源替代,看起来还在早期开发阶段,对功能要求不多的 macOS 用户可以试试。如果你不知道 RayCast 是什么:这是一个可以随时用快捷键呼出的输入框,你可以用这个输入框启动 App、计算、查词典、搜索网页、搜索文件等等。装了这类软件之后,你就不需要再依赖 Spotlight(聚焦搜索)了。

访问: RustCast

当下

你好,Codeberg!

上周分享了《 逃离 GitHub 指南 》,这周就付诸行动了,起因是我在更新 Libra.js 的时候突然想到:为什么不直接把仓库搬去 Codeberg 呢?

于是我就这么干了。

如果你不知道 Codeberg 是什么:这是一个总部位于德国柏林的非盈利组织,可以说是世界上最自由的 Git 仓库托管平台。如果你不喜欢 GitHub 这样的被大科技公司控制的平台,Codeberg 是很好的选择。我打算给 Codeberg 捐款,可惜 PayPal 支付出了些问题,刚好春节回家了,银行卡没带在身边,只能等到三月份回去之后再操作了。

我其实还有一个自建的 Forgejo 实例,叫作 Hydra ,还没有在博客正式介绍过。注册了 Codeberg 账号之后,我也开始思考一个问题:如果使用 Hydra,就对外来的贡献者不太友好,毕竟 Hydra 是不开放注册的;那么,应该在什么时候使用 Hydra 托管代码,什么时候将代码托管到 Codeberg 这样的公共平台上呢?

目前我的想法是:将基本不会有贡献者的个人项目放在 Hydra 上(比如这个博客、一些网站的源代码、某些软件的配置文件等等),将欢迎其他人一起开发、有收集反馈的需求的项目放在 Codeberg 上。诚然,从 GitHub 换到 Codeberg,有机会发现我项目的人会少很多,但说真的,就算是我几年前写的有 500+ Stars 的 Typecho 主题 ,用户也都不是在 GitHub 直接发现这个项目的,而是从 Typecho 主题目录和我的博客发现的。新项目用 Codeberg 托管,反而能让更多的人意识到:噢,原来我可以离开 GitHub。

至于 GitHub 账号…… 斟酌过后,我决定暂时保留。一方面,诚实地讲,我的确舍不得那几百个 Stars 和一百多个 Followers;另一方面,不少我常使用的开源项目都在 GitHub 上,要给他们提 Issues 和 Pull Request,没有 GitHub 账号是不行的。我只是不会在 GitHub 上托管新的项目了,并且会逐渐把仍在维护的项目从 GitHub 迁移到 Hydra 或者 Codeberg 上。

整体而言,我是这样想的:

  1. Codeberg:为开源世界做贡献的主要阵地。
  2. Hydra :托管小型的个人项目,博客、配置文件等等;也会镜像我发布在 Codeberg 上的仓库作为备份。
  3. GitHub:为那些没有托管在其他平台上的项目做贡献的地方(尽管不会很多)。

如果你想的话,可以关注我的 Codeberg 资料页 ,这里还没有太多的东西,但是会慢慢丰富起来的。

现在可以手动切换亮色和暗色模式

上周 在周刊的开头表示自己正在重新思考博客的一些 UI/UX 设计,并且在联邦宇宙上发起了 投票 ,结果令我震惊——原来我才是异端吗?!

macOS 会根据当地日出日落时间在亮色和暗色模式之间切换,我也花了不少心思让 NeoVim 和自己的网站自动跟随系统配色,结果调查之后才发现,原来大部分人都不会切换操作系统的明暗配色!而且,还有一部分人出于隐私考虑,禁止网站读取操作系统的明暗模式(因为暗色模式的用户很少,追踪器可以用这个特征生成指纹),所以也有手动切换的需求。

总而言之,现在你可以在网站首页的导航栏下找到一个选择框,可以在「自动」「亮色」和「暗色」三种模式之间切换。

顺带一提,把代码推送到生产环境之后,我还遇到了一些奇怪的小插曲。如果你对这些技术细节感兴趣,可以到后花园阅读 这篇笔记

咖啡因……

回家之后离开了自己的吧台和咖啡器具,我像头慌不择路的驴一样翻出了一台半年没用过的廉价美式滴滤机。由于我连称都没有,磨豆也只能用我爸那台意式咖啡机上自带的磨豆组件,更是控制不好分量和水量,于是就…… 毫不意外地喝多了。

我怎么就没有意识到那台美式滴滤机做出来的一壶咖啡是给一家人喝的量呢?

总而言之,刚回家的那天下午,我发现自己喝过咖啡之后浑身不舒服,大脑…… 感觉很奇怪,有点不像自己的了。我很快发现自己的眼皮在规律地抽搐,整个身体进入了一种难以言喻的奇妙状态,在床上躺了好久都没缓解。当时我就知道,我整个下午都不会好受的,因为咖啡因代谢需要 4~6 个小时。

不知道你有没有读过我在本周二发布的《 如何用电车难题理解 Git 》。我很确定,这篇神得没边了的文章有一半是咖啡因摄入过量导致的。

回忆了一下,以前的我尽管每天喝两杯,但每一杯咖啡也就 200~230 ML 的量,而我估计我刚回家的时候喝下的那一壶咖啡,应该有 500 ML 左右。

总而言之,喝咖啡要节制!

切片

  • 本周发现,我一直在用并且非常喜欢的 RSS 阅读器 NetNewsWire,竟然比我老!它今年 23 岁 了。不过说实话,有什么好震惊的,我本来就还年轻啊
  • 一直在使用 DuckDuckGo 作为搜索引擎,它没什么不好的,但搜索这件事情的私密程度堪比写日记,如果有开源且自托管的方案就太好了。之前受 Taxodium 的推荐,我还认真考虑了一下要不要订阅 Kagi,一个付费、无广告、注重隐私的搜索引擎。最近我一直在蹭 Southfox 搭建的 SearXNG 实例使用,体验非常丝滑。简单来说,这是一个会在用户搜索内容时从服务器请求其他搜索引擎,然后把内容聚合在一起的开源软件。SearXNG 还支持在访问某些网站时跳转到第三方的隐私前端(比如 Nitter )。我现在也很想自托管一个,就是最近做了太多运维工作,有些疲乏了,而且不清楚 SearXNG 的资源占用如何。
  • 情人节那天,Internet Archive 发布了一些复古的、保留到现在的 情人节插画设计 ,这些绘画风格很吸引我。我最喜欢的是 这张 ,是一本儿童杂志的封面。
  • 如果没记错的话,今年情人节应该是世界上第一台通用计算机 ENIAC 的 80 岁生日。

  1. Apple Music 邀请有名气的艺人来分享音乐的一系列电台节目。 ↩︎

  2. Scramble 作为动词的意思是「爬」,Scrambler 可以理解为「爬行者」;在通信领域,Scrambler 指无线电的「保密器」,这层意思和小说中的设定可能会更契合一些。顺带一提,我之前也用 Scramble 这个词生造了一个叫作 Scramby 的词用来写怪谈作品,你可以在 Backrooms 读到: Entity 60 - “Scramby” 。 ↩︎

  3. 最开始写下这行文字时,我的了解不算全面,这里需要补充一些信息。如今的 GnuPG 实际上已经不支持最新的 OpenPGP 标准了,而是转而制订了新标准 LibrePGP ,这个标准分裂了。Sequoia 实现的才是最新的 OpenPGP 标准。嗯…… 总之 PGP 系的软件和标准还挺乱的。 ↩︎

  4. 顺带一提,Soatok 似乎也不太喜欢 Matrix 这个通信协议,他在 2024 年 8 月写过一篇 文章 解释 Matrix OLM 库的安全性问题;不过,时间已经过去一年多了,文章所述问题是否被解决,我就不太了解了。他比较认可的加密通信软件是 Signal。不过他也承认,对于那些不愿意使用手机号注册的非常在意隐私的技术用户而言,Signal 不太合适,也没有其他更好的选择。所以…… 还是继续用 Matrix 吧…… ↩︎

  5. 来源: BBC News (Internet Archive)  ↩︎

  6. 我还想补充的是,最近 Discord 也开始向全球的用户自发地进行年龄认证。没有政府要求他们必须这么做。 ↩︎

  7. 顺带一提,我之前还在 Backrooms 中文维基的时候,由于我们有 17 岁的年龄限制,很多社区维护工作都是在调查和处理那些在自己入站申请书上撒谎的小孩子。不过这个年龄认证也不会真的取证,只是要求成员「表现得成熟一点」。说真的,你难以想象这些小孩子能在小社群里闹出什么事情来…… ↩︎

关于本站

2026-02-14 19:45:13

欢迎来访,这里只有真情流露和赤裸的思考。

这是什么地方,谁在这里?

这是一个…… 呃…… 博客?在打理这个地方的人,名字叫作 Eltrac

之所以在回答「博客」一词时迟疑了片刻,原因是,坚持写作一年半过后,我已经有些不确定这个网站是否符合「网络日志」(Web log)的本意了。比起个人化的记录,它更像是我思维的载体,我在这里思考、批判、学习和感知,尝试理解世界、理解生命、理解自己、理解爱。

我更希望把《極客死亡計劃》视作我的作品,而不是过于个人化的东西,尽管作品必然是自我的延伸,任何的表达也必然是主观的。我想保留适当的节制、严谨和固执面对这个网站。你或许能理解,人在写日记和做毕业设计时的心境是完全不同的,而我在这里写作时,处于两者之间。

我要大方地承认,我在互联网上写东西就是为了被人看见的,那些只是为了自己而写的东西,全都在我的本子里,用墨水写就,从未变成比特流传入互联网。

然而,我也不是抱着写高考作文那般的谄媚心态而创作的。尽管希望被看见,我也只会写自己的所思所想、所作所为、所感受到的和体验到的。我既是在为被看见而写,也是在为自己而写。如果你允许的话,我会有些不知天高地厚地说,我是为了世界而写

好吧,那你写些什么呢?

一切能引发我求知欲的东西,一切引发我强烈感受的东西,和一切引发我思考的东西。

具体来讲,首先,我本人是软件工程专业的学生,这里虽然没有铺天盖地的代码片段和运维记录,但会有我对于软件、开源和技术自由的思考,如果我发现了一些有趣的新技术、学了一门新的编程语言、探索了某种实现软件的思路,我也会把相关内容和感受写在这里。

我对人文社科也很感兴趣,包括但不限于哲学、语言学、社会学、心理学、经济学和他们的交叉领域。我没有能力撰写专业的相关著作,但我学到的一些理论和思维模型,的确塑造了我的认知,也继而激发了我的写作。

广泛地来说,我希望把自己称作「人类观察者」。我并不恐惧社交,但我的确不太喜欢和人扎堆。你可能猜得到,我和大多数人(至少是大多数同龄人)都没什么共同话题,跟年长者更是如此。因此,在现实生活中,我往往不会给人很高能量的回应,更多地,我倾向于观察和解读旁人的言谈、行动和语气,找出背后的原因和共性。这也对应了我对心理学的兴趣。

多说无益,如果真的感兴趣,那还请你去读一读我写的东西吧。你可以在 标签 页面找到一些话题,在 档案馆 找到本站所有的页面,也可以在 这个页面 找到我自认为写得不错的文章选集。


这个网站是怎么设计和创建的?

技术层面的问题,你可以阅读 考锹 页面。

关于设计网站时的所思所想,以及选用某一项技术时所做的考量,我有写过一些相关的文章。《 极客死亡计划书 》系列包含了我在不同时期对博客写作和独立互联网的思考,此外,你还可以读一读《 「极客死亡计划」的设计哲学 》这篇文章。

要怎么给你的文章评论?

简单来说,你需要一个 联邦宇宙 账号,然后在一篇文章对应的帖子下回复即可。也欢迎你直接在联邦宇宙上提及我的账户 @[email protected],附上文章链接和我交流。

或者,你也可以向本站发送 Webmention

如果你不知道我在说什么,不熟悉以上两种技术,请阅读 这篇文章 1,里面有更详细的解释。你也可以省去这些麻烦,直接发送电子邮件到 [email protected]

还有别的吗?

我在 The Backrooms 后室中文维基写了一些东西,这是我的 作者页 。目前我已经不在那里写作了,也辞去了网站的版主职位。我偶尔还会继续写一些小说,你可以在 这个页面 里读到。

这之外的东西,还请到 我的个人主页 查看。

回到这个网站的话题上来,还有其他一些页面你可能会感兴趣:

  • 林卷 :来自互联网其他地方的链接。
  • 考锹 :了解这个网站是如何构建的。
  • 洞察 :博客的字数统计、内部连接图谱等等。
  • 标记 :来自所有文章的高亮标记,我认为写得不错的句子。
  • 赞助 :如果觉得文章不错,欢迎打赏,这里也列出了所有慷慨的赞助者名单。

极客死亡计划还有一个后花园,叫作 罐子 ,这里存放着草稿、废稿、笔记和各种东西。



  1. 至于为什么不使用常规的评论系统,也请阅读这篇文章了解。 ↩︎

考锹

2026-02-14 16:29:46

一些出版物的扉页或最后一页会附上说明,交代作者信息、出版方和印刷方式等等,这个页面叫作版权页,英文单词是「Colophon」。这个单词还是一种锹形虫(考锹甲属)的学名,我取了这一种甲虫名字的前两个字作为本页标题。

/colophon 页面的概念来自 IndieWeb

技术栈

本站是静态网站,使用全世界最快的静态网站生成器 Hugo 构建。没有使用任何主题,模板文件直接写在 layouts/ 目录下,CSS 引擎使用 UnoCSS 。源码见此: eltrac/geedeapro

本站没有常规的评论系统,文章的评论、点赞等互动信息与 我的联邦宇宙账号 互通。互通是通过 Brid.gy 实现的,它会每隔一段时间抓取我的联邦宇宙账号,一旦发现新的回复和点赞,就会将互动信息以 Webmention 的形式发送到这个网站上。

本站的 Webmention 接收端由 webmention.io 提供服务,未来会部署自己的 Webmention 后端服务,不依赖第三方。Webmention 不是通过 JavaScript 动态抓取的,而是通过 Actions 工作流 直接写入 Git 仓库中的 JSON 文件,再渲染到静态网页中的。也因此,发送到本站的 Webmention 会等待最多半个小时的时间才会显示在网页中,来自联邦宇宙的互动也会有延迟。

构建和部署

本站部署在 Contabo 的 VPS 上,一家德国的云计算公司,服务器位于欧盟地区,数据受到 GDPR 法律保护。我使用 Caddy 运行 Web 服务器并自动签发 SSL 证书;没有什么复杂的配置,仅仅是写在 Caddyfile 里的一行 file_server 而已。

网站的代码托管在名为 Hydra 的 Forgejo 实例上,CI/CD 工作流也运行在自己部署的 Forgejo Runner 里。如果你不了解这是什么,可以简单理解为自有的 GitHub 和 GitHub Actions。至于部署,是通过 Webhook 在每次 git push 推送新代码到 Forgejo 仓库后,触发 VPS 上的构建脚本进行的,所以不能算严格意义上的「部署」,网站是直接在宿主机上「构建」的。

如果你问我为什么部署静态网站还要用一台 VPS:那是因为我还有别的服务跑在上面,以及我非常在乎我的数字主权。

发布流程

我在本地使用 NeoVim 编辑器,以 Markdown 格式撰写文章,用 TOML 格式编写页面元数据(Frontmatter)。我使用 Git 作为版本管理工具,当有改动被推送到自己部署的远程仓库之后,就会触发网站重新构建,新页面也就在这个过程中被发布。

本站的文字内容全部由我本人撰写,不接受投稿( 周刊 会接受推荐,但仍由我本人撰稿),也绝对不会使用 AI 生成。

性能

本站的 PageSpeed Insight 得分如下,我会加油做得更好的!

本站使用 instantpage.js 优化页面打开速度。

数据收集

本站没有 Cookie。

本站会在你的 LocalStorage 里存储三项数据:

  • geedeapro.fediInstance:如果你使用了文章互动区的联邦宇宙表单并填写了你的实例域名,这个域名会被保存在本地。
  • geedeapro.theme:如果你切换了配色方案(自动、亮色或暗色),网站会记住你的选择。
  • geedeapro.ui.seenAndUsedBat:本站的返回顶部按钮的样式是一只蝙蝠,他叫 Batrick,点击它你就能回到页面顶部;如果你见过并使用了 Batrick,这个行为会被保存,这之后 Batrick 就不会告诉你他是来「带你上去」的了,默认你已经知晓如何使用返回顶部按钮。

以上数据全都储存在本地,不会被发送到服务器,你可以随时删除。

流量统计使用注重隐私保护、不跟踪用户的 GoatCounter ,统计数据是 公开的 。我只能看到「有多少人访问了某页面」「我的访客中有多少人使用某操作系统、某浏览器」「我的访客中有多少人来自某某国家」,但我没有办法知道这个人是谁,因为本站不会跟踪用户。如果你想完全悄无声息地访问我的网站,请在 URL 最后加上 #toggle-goatcounter,这样你的踪迹就完全不会被记录。

其他

  • 搜索功能使用 Pagefind 实现。
  • 图片等媒体文件由 Cloudflare R2 托管,数据位于欧盟地区,我正在寻找价格合适且总部位于欧盟地区的对象储存提供商。
  • 网站的图标来源
  • 文章标题中使用的 CSS 扭曲效果来自 endtimes.dev
  • 网站背景纹理来自 Hero Patterns

网页即列表!

2026-02-13 22:30:17

购入 eltr.ac 域名之后,我打算把「个人身份」和「作品」分开,也就是用 eltr.ac 域名代表我在互联网上的身份标识,用 geedea.pro(也就是这个博客的域名)代表「极客死亡计划」这个作品。是的,我并不打算把我的博客当作我网络身份的载体,更多地是视作独立的作品。目前我的联邦宇宙实例用的是 eltr.ac 域名,我的 Matrix(一个去中心化的即时通信协议)服务器也是用这个域名,电子邮箱也是这个域名,所以就有了:

不过,要是有有心人尝试访问 www.eltr.ac,只能看到一个很简陋的页面。既然要把这个域名用作身份标识,那在万维网层也要有一个足够有个性的页面吧,就算仅仅是当作导航也好。

编程语言的选择

一开始我用 Hugo 做了一个简陋的网站,还参考 slashpages.net 添加了一些足够「个人化」的页面,但总觉得不满意。仔细想想,这竟然已经是我的第三个 Hugo 网站了!第一个是这个博客,第二个是这个博客的 后花园 。尽管我很喜欢 Hugo,这个用 Go 语言开发的静态网站生成器也是全世界最快的生成器,但也不能用 Hugo 解决所有的网站构建需求吧?况且,我只需要几个页面,用非常擅长处理大量 Markdown 文件的工具来构建网站,属实有点像「用大炮打蚊子」。再者,我也对 Go HTML Template 感到厌烦了。

简单的、不常修改的静态页面,要是用我熟悉的 Vue.js(或者…… React)来做,也有过度工程之嫌。貌似手写 HTML 就能胜任,不过要是真这么做,那我还不如直接加入 Neocities 呢。之前被迫写了太多 Java 代码,也不可避免地接触到了很多 XML(Java 的项目管理工具 Maven,配置文件就是用 XML 写的;还有老旧的 MyBatis,编写数据表和 Java Bean 的隐射也要写 mapper.xml),继而对 HTML 也感到厌烦了。自动补全固然好用,但难道就没有更优雅的解决方案了吗!

前端开发者应该都熟悉「DOM 树」的概念,HTML 所代表的就是一种树形结构,根节点是 <html> 标签,<head><body> 组成了第一级的子节点,然后继续往下生长。这样想就很简单了,现代编程语言里能够代表树的数据类型多了去了,只要用某种语言先编写好 DOM 树的结构,再转换成 HTML 字符串就好了。那么,能够表示 DOM 树的最简单的结构是什么呢?

广义表就是个例子,这是数据结构里的概念,更接地气的名字是数组、元组和列表。比如,用 JavaScript 就可以这样写:

const html = [
 { name: "head", innerHTML: [
 { name: "meta", attribute: {
 rel: "stylesheet",
 href: "/main.css"
 } }
 ]},
 { name: "body", innerHTML: [ ... ] }
]

停停停,用 JavaScript 数组和对象写 HTML,还不如直接手写呢。有没有办法全部简化成数组(列表),不使用对象呢?比如这样:

const html = ["html",
 ["head", [ ... ]],
 ["body", [
 ["p",
 [ "class", "paragraph font-sans",
 "id", "paragraph" ],
 "Hello World!"
 ],
 // ...
 ]]
]

你等一下,你再继续写下去就是在抄袭别人的 Lisp 传教文了。

是的,既然我们已经知道嵌套的列表实际上就是树的结构,那为什么不可以直接用列表来写 DOM 树呢?既然我们已经把编写 HTML 页面变成了对列表的处理,那四舍五入就得到了「列表处理」,List Processing,所以,答案就是 Lisp!

老实说,除了我本来就对这门编程界的拉丁语感兴趣之外,促使我学习一门 Lisp 方言的最大推动力就是:用它来写网页的方式简直是太优雅,太天才了! 这下谁还需要依赖 Tree-sitter 和编辑器插件自动闭合 HTML 标签?还有那可恶的 JSX,也可以说再见了。现在只需要闭合一大堆没完没了的括号就好了。

技术栈的选择

我选择的 Lisp 方言是 Clojure ,至于为什么选它,原因是…… 呃, 矩阵 上有一只 狐狸 诱拐我进了这个兔子洞。无论如何,我就先从 Clojure 开始用着吧。要使用 Clojure 编写程序,第一步是——

安装 Java?!

没错,Clojure 是运行在 JVM(Java 虚拟机)上的,这下再讨厌 Java 都逃不掉了,不过也还好,不需要亲手碰肮脏的 Java 代码,还能享受偷窃 Java 库来用的快感。Minecraft 玩家们的电脑里应该都有 Java,所以可以跳过这一步了。

最常用的 Clojure 开发工具是 Leiningen ,用来创建项目、管理依赖、运行和编译项目等等。要将 Clojure 的数据结构转换为 HTML,可以使用 Hiccup 这个库。既然我们都用 Lisp 写 HTML 了,那为什么不用 Lisp 写 CSS 呢?于是我很快发现了 Garden 这个库,用于把 Clojure 转译为 CSS。

现在准备就绪了,开始创建项目吧!

顺带一提,我准备把这个新网站命名为 capricorn(摩羯座),至于原因,仅仅是因为它是十二星座里首字母和 Clojure 相同的一个。

lein new app capricorn
cd capricorn
nvim project.clj

编辑 project.clj,把需要的依赖添加进去,也就是刚才提到的 Hiccup 和 Garden。

:dependencies [
 [org.clojure/clojure "1.12.2"]
 [hiccup "2.0.0"]
 [garden "1.3.10"]]

哦对了,如果你还不了解 Lisp 的语法,让我花点时间来给你解释一下:列表的第一个元素是函数名,剩下的都是参数,比如 (println "Hello World") 就是打印 Hello World 字符串。在 Clojure 里,列表有多种类型,除了最基本的作为程序结构的 (),Clojure 还用其他的括号表示不同的数据容器:'() 是列表,[] 是向量,{} 是映射(也就是键值对),#{} 是集合。Clojure 里还有一种特殊的数据类型叫作「关键字」,写成这样 :keyword,我喜欢把它理解为不需要提前定义的枚举类型。

好了,现在你可以理解 Clojure 程序了,我们继续吧。

手搓静态网站生成器

静态网站生成器(Static Site Generator,SSG)这个东西可谓是遍地开花, Cryogen 就是用 Clojure 写的一个 SSG。不过,由于我的需求不是额外制作一个博客,不需要将 Markdown 文件或 Org-Mode 文件构建为 HTML 的功能,我只需要把 Clojure 的数据结构编译成 HTML 字符串,然后写入一个文件即可。听起来不难,所以动手自己写吧!

用 Hiccup 编写 HTML 结构非常简单,以下是一个例子:

(require '[hiccup2.core :as h])
(str (h/html [:span {:class "foo"} "bar"]))

看起来有点难懂,没事,拆开来就好了:

  1. require 显然是引入,我们这里把 Hiccup 的核心库以 h 这个名字引入了。
  2. str 是 Clojure 标准库里的函数,顾名思义,就是用来把其他数据类型转换为字符串的。
  3. h/html 中的 h 是我们刚才引入的 Hiccup 核心库,html 是这个库提供的函数,用于把 Clojure 的数据结构转换为 HTML 表示;这里接收一个向量类型的数据,也就是 [] 包裹的列表。
  4. [:span ...] 显然是 <span> 标签的表示,这个向量由三部分组成:
    1. :span 是我们刚才提到的关键词类型,这里作为 HTML 标签名。
    2. {:class "foo"} 是一个映射,:class 关键词是键(key),"foo" 字面量是值(value),这个映射会被解析为 class="foo";我们还可以继续写更多的属性,比如 {:class "foo" :id "bar" :width 200},奇数个元素是键,紧跟其后的偶数个元素就是对应的值。
    3. "bar" 是这个 <span> 标签的内容,也可以替换成向量,也就是继续嵌套 HTML 结构。

总而言之,这段 Clojure 代码构建了形如 <span class="foo">bar</span> 的 HTML 结构,并将它转换为字符串输出。Hiccup 还提供了非常好用的语法糖,如果要编写 <span class="foo" id="bar" />,可以写成 [:span.foo#bar]。 尽管看起来有点奇怪,但你很快就会意识到这样写 HTML 有多爽了。

我们先来解决一个关键的问题,静态网站生成器除了把某种形式的数据转换为 HTML 字符串,还应该把得到的结果写入 .html 文件。接下来我们要研究一下如何用 Clojure 进行 I/O 操作,文档里写的是使用 spit 函数将内容写入文件,然后……

等等,就没了?!

看起来是这样的,在 Clojure 里,你不需要创建 Writer,也不需要创建什么 File 对象,也不用额外引入一个 I/O 库1,你只需要使用 spit 函数,把内容「吐」出来。这个函数接收两个参数,第一个参数是文件路径名,第二个参数是要写入的内容。所以,我们可以编写这样的 Clojure 程序:

(defn -main
 [& args]
 (spit "public/index.html"
 (str (h/html
 [:p "Hello World"]))))

接下来,运行 lein run,然后你就会得到一个内容为 <p>Hello World</p>index.html 文件。2

正式开发之前,我们大概是需要一个跑在本地的 Web 服务器的。如果是 JavaScript 技术栈,可以用 npm run dev 或者 pnpm dev 这样的命令。这样一个有些原始的项目应该怎么办呢?

cd public
python -m http.server 8080
open http://localhost:8080

There you go.

如果不介意没有热重载,每次更改之后需要手动运行程序然后手动刷新浏览器的话,就已经可以上路了。

模板!组件!

我们先把头拆出来吧。

(def head
 [:head
 [:title "Eltrac"]
 [:meta {:charset "utf-8"}]
 [:meta {:name "viewport" :content "width=device-width,initial-scale=1"}]])

def 是 Clojure 里定义符号(变量)的方式;如果要定义函数,就用 defn。由于我这个小网站只有几个页面,不在乎标题的话,<head> 可以直接复用,所以就不需要定义成函数了。

再把刚才的 Hello World 放到 <body> 里。由于不同页面的内容肯定是不一样的,所以把 body 定义为函数比较合适。

(defn body [content]
 [:body
 [:main content]])

然后拼接在一起,放在 <html> 里,最后再转成字符串写入文件。

(defn -main
 [& args]
 (spit "public/index.html"
 (str (h/html
 (h/raw "<!DOCTYPE html>")
 [:html head (body
 [:p "Hello World!"])]))))

其中 h/raw 函数可以防止 Hiccup 自动将 <> 转义,用来声明 DOCTYPE 是必要的。最后就得到了:

<!DOCTYPE html>
<html>
 <head>
 <title>Eltrac</title>
 <meta charset="utf-8" />
 <meta content="width=device-width,initial-scale=1" name="viewport" />
 </head>
 <body>
 <main>
 <p>Hello World!</p>
 </main>
 </body>
</html>

显然,完全不需要什么 defineProps(),也不用额外学习怎么在父子组件之间传递数据。在 Clojure 和 Hiccup 的世界里,页面组件就是函数。怎么给函数传参数,就怎么给组件传参数。我们直接用 Clojure 处理向量,最后再把向量们汇总在一起,用 Hiccup 处理为 HTML,再转成字符串输出到文件里。这样一来,我们就能用 Lisp 编写 HTML 页面了。

Build website with Style

接下来要给网站添加 CSS,使用之前添加的 Garden 库。这个库的语法和 Hiccup 非常相似,最关键的函数是 css 函数,和前面的 html 函数一样,它接收向量作为参数,输出的是 CSS 字符串。

我一直是行内 CSS 的信奉者,少一个 .css 文件能减少服务器需要传送的文件数量,并且不会被缓存,用户能够在网站更新后立刻看到改动。只要 CSS 文件不是太大,我都建议使用行内 CSS,也就是 <style> 标签。

那给这个 Clojure 静态网站添加样式就非常简单了,只需要在 head 变量里加一个 <style> 标签,为了方便,我们再定义一个 style 变量。

(require '[garden.core :as g])

(def style (g/css
 [:body {:margin 0}]) )

(def head
 [:head
 [:title "Eltrac"]
 [:meta {:charset "utf-8"}]
 [:meta {:name "viewport" :content "width=device-width,initial-scale=1"}]
 [:style style]])

然后就可以愉快地用 Lisp 写 CSS 了!至于 JavaScript,我暂时还不打算添加。如果你需要,可以使用 ClojureScript ,也就是用 Clojure 来写 JavaScript。这样一来就能用 Clojure 写遍 HTML、CSS 和 JavaScript,实在是惬意。

生成多个文件

刚刚我们把所有的 HTML 汇总到了 main 函数里,生成了一个 index.html,一个网站显然不能只有一个页面。我们需要这样的结构:

(def page_index (base [:p "Hey!"]) )
(def page_contact (base [:p "Contact me!"]))

(defn -main [& args]
 (site {:index page_index
 :contact page_contact}))

简单来说,我们在主函数里调用名为 site 的函数,传入一个映射,映射的键是页面名字,值是对应的 HTML 结构。定义 HTML 结构时还用到了自定义的一个 base 函数,理解成模板就好了。

site 是怎么实现的呢?

(defn pathname [pname]
 (if (= pname "index") "public/index.html"
 (str "public/" pname "/index.html")))

(defn page [pname, html]
 (.mkdir (io/file "public"))
 (when-not (= pname "index")
 (.mkdirs (io/file (str "public/" pname))))
 (spit (pathname pname) (str html)))

(defn site [page_map]
 (doall
 (map (fn [[pname html]]
 (page (name pname) html))
 page_map)))

然后就能得到这样的目录结构。

public
├── contact
│   └── index.html
└── index.html

再讨论细节就有些繁琐了,总之,现在有一个能够生成静态网站的框架了,并且一共才写了不到 100 行代码。为了方便后续开发,我把上述功能拆成了几个模块,源码可以在 我的 Forgejo 实例 上查看。

最后

Capricorn 的实现不是最优的,也不能称得上真正的静态网站生成器,但对于目前的问题规模(生成只有一两个页面的简单网站)而言,已经很好了,不需要再扩张,后续可能会加上自动生成的导航栏,用 ClojureScript 写一两个小组件获取 NeoDB 和 WakaTime 数据等等。要添加这些功能,在 Lisp 里就显得很容易,只需要用 (defn) 定义一个函数就好了。这就是代码即数据的威力啊(并非)

我学 PHP 时,写出来的第一个程序是 一个 Typecho 主题 ;学 Lua 时,写出来的第一个程序是 一个简陋的肉鸽游戏 ;用 Go 语言写的第一个程序是 一个简单的 CLI 工具 。现在学了 Clojure,写的第一个程序竟然是生成固定内容的网页生成器。这么看来,我果然是退步了吧!

学 Lisp 的确有很奇妙的感受。《代码简洁之道》这本书里,作者认为函数里最好只有两三行代码,并且所有代码都要在一个抽象层次上,这需要克制、熟练和清晰的头脑。神奇的是,在 Lisp 里,这是非常自然的,因为一旦我写出了不太优雅的代码,代码的结构也会随之变得扭曲起来,比如这样:

(def pages ["index" "contact"])
(def nav
 [:nav.nav [:ul
 (for [p pages] [:li
 [:a {:href (str "/"
 (if (= p "index") "" p))} p]])]] )

在 Lisp 里面写出太长或者耦合度太高的函数,代码的结构就会变得很奇怪,带来清晰的视觉反馈;写出简单小巧的函数才是自然的。虽然不知道这是不是所有 Lisp 程序员的感受,但 Lisp 的确让我开始思考程序结构的美感。

此外,如果你是个资深的 Lisp 程序员,读到我在文章里写出的 Lisp 代码,还请不要气昏过去。

先这样吧。各位,我要去研究 Paredit 了。


  1. 如果要创建目录(mkdir)的话,还是要引入 Java 的 I/O 库的,spit 只提供了创建和写入文件的能力,不能处理目录。如果要读文件,还有一个对应的 slurp(吸溜)——真的是…… 好简洁明了的命名方式。 ↩︎

  2. 第一次觉得 <p> 标签特别可爱,像一张吐舌头的脸::p。还有 <a> 标签::a XD ↩︎

如何用电车难题理解 Git

2026-02-10 23:53:53

你是一个疯狂的哲学家,你意识到反思、设想和解构已经无法满足你对人类道德的求知欲了,于是你决定开始实验。就这样,你找到了一些铁轨、一辆火车、一根拉杆和一些必要的机关,哦对,还有绳子,现在就差受害者了。

挑选受害者

你可不想过于随意地进行这场伟大的实验,有幸参与实验的人也是需要层层筛选的。总而言之,你找到了一群愿意或不愿意的受试者,让我们把他们称作 A 组。尽管你在选取受试者时也是带有目的的,但在正式开始实验之前,理应再做一次筛选。于是,你把 A 组中的一部分人绑上了铁轨,让另一部分人直接离开了,我们把这一部分有幸被绑上铁轨的人称作 B 组。

读者可能猜到了,一开始进入这场思想实验的 A 组就是 Git 的工作目录(work tree),B 组自然代表从工作目录中挑选出来的布置区(staging area),那些被哲学家打发走的人就是被丢弃(discard)或者说复原(restore)的工作。当然,挑选到布置区的人们也并非是直接为伟大的哲学事业做出了贡献,哲学家还需要规划思考一下,应该让火车怎样碾过他们,最好是要绘制一个路线图,这一步相当于是承认(commit)了这些人有真正参与到这场实验的资格,当然,哲学家也因此犯下(commit)了杀人罪。

简单来说,哲学家在这里做了三次选择:

  1. 在近乎无限的茫茫人海中,把受试者带到工作区域来;
  2. 在工作区域中,挑选一部分人进入布置区,另一部分人留在工作区稍作等候,或者直接打发走;
  3. 如果不需要对布置区里的人选进行调整了,就可以决定火车压过的路线了!

当哲学家命令火车向前行驶,压过铁轨(staging area)上被绑好的人们之后,我们可以说:他提交(commit)了他的工作。

显然,在这篇文章中,我们把对代码的改动抽象为「工作」,而在电车难题的比喻中,我们用人来代指「工作」。

嗯,让我们来看看我能用这个不太道德的比喻扯多久吧。

无人生还

现在,哲学家已经把一些人绑在铁轨上了,距离电车压向他们已经不远了!从某种意义上来讲,他们是薛定谔的猫,几乎是已经死了,但又还没有死。无论怎么样,他们都可以被当作 Git 历史的一部分了。

如果你数学比较好的话,你可以把电车和这些人的距离理解为「无穷小」,而不是 0 或者负数。哲学家好像和上帝关系很好,或者如果你不信神的话,也可以说哲学家和疯狂科学家关系很好,他从隔壁爱因斯坦那里学会了和时间作对,于是成为了四维生物,能够在时间维度上行走。总之,哲学家可以在电车真的压向这些人之前做任何操作,甚至救下他们,但对这些凡人自己来说,他们已经必死无疑了。

这个时候,哲学家突然想再挑一些人上来。可是,就像我们说的,电车正在无限逼近,把人直接往铁轨后面叠加固然没问题,但属实缺乏一些工程美感(是的,哲学家也是思想的工程师!)。主要的原因是,哲学家自己其实也不太确定要不要真的把某些人纳入这场伟大的实验中,这可怎么办呢?

答案就是:分支!

我们假设,哲学家本来想要把奴隶主全都碾死,可转念一想,又觉得应该把所有的资本家也都放在铁轨上,但是他并不确定,可以确定的是,他想要奴隶主死,至于资本家,还要再斟酌斟酌。

于是他把奴隶主放在最主要的,车子一定会碾过的轨道上——我们把这条轨道称作主分支(master1)。至于那些资本家,先放在另一条轨道上,我们给这条分支随便起一个名字,比如 foo,原因是这些资本家挺蠢的(fool2),但又有些实力,只能算四分之三的蠢吧。

于是现在有了两条轨道,电车有两条行驶路径,哎呀呀,真是难选择啊。到底应该让车子往哪边开呢?

如果哲学家决定不让资本家献身了,那他可以把 foo 分支删掉,或者就放在那不管。反正,只要他不拉动拉杆,电车就只会碾过主分支上的奴隶主们。

如果哲学家下定决心,觉得资本家也该死,那么他有两个选择,第一个是——

合并!(merge)

需要注意的是,如果仅仅是把 foo 分支和主分支接轨,那是无法做到「无人生还」的。我们可以看到,轨道是在第 4 号奴隶主产生分支的,而在那之后,主分支上还相继出现了 5 号和 6 号奴隶主。如果让电车从分叉处驶入 foo,又在之后回到主分支,那分叉口之后才出现的 5 号和 6 号不就幸免于难了吗!

所以,我们要把 5 号和 6 号奴隶主移动到第二个分叉口,也就是电车回到主分支后会到达的地方。这样就能保证合并之后,不会有一个人被落下。

“可是,”你可能会说,“这样不是太费铁轨了吗?而且好复杂。”没关系,我们还有第二个选择——

重整!(rebase)

对嘛,大家都躺成一排,这样就方便了。

哲学家的时间旅行魔法

在此之前,我们一直使用数字对受试者进行编号,这很直观,可是真正的哲学家却不会这样做。实际上,哲学家使用一种凡人们读不懂的语言为每个人起名,这种语言由数字和字母组成,十分神秘,它长这个样子:9a0f7b785e

这种编号是怎么构建出来的呢?很简单,只需要对比这位受试者相比上一位受试者有何不同,并从特征中提取摘要,再进行哈希运算即可得到。

不过,即使是哲学家也记不住那么多人名,因此,哲学家规定,在某个时刻下,最后一个会被电车碾压的人为人群之首,记为 HEAD。不难理解,HEAD 也就是哲学家最近挑选的、最新鲜的工作成果。不过,这其实还要更复杂一些。

我们知道,哲学家是四维生物,可以在时间上行走,他可以查看(checkout)任意一个时间点下轨道的状态,以一种凡人没法做到的方式回顾历史。对于哲学家来说,HEAD 是他当前查看的那一个工作,也是他开始工作的地方——如果哲学家要添加新鲜的人类样本,或者创建分支,那就是从 HEAD 开始的。

我们不是知道哲学家可以给铁轨创建分支吗?一般来说,HEAD 就是就是当前分支下最新的那一条工作。如果哲学家念出咒语 git checkout foo,它就会来到 foo 分支,准确来说是 foo 分支下最新的那条工作;要回到主分支,只需要念出咒语 git checkout master。由此可以发现,轨道的分支实际上就是指向当前分支最新改动的一个指针。

哲学家还会使用一种特殊的相对记名法,他使用 HEAD^ 表示 HEAD 的前一条,使用 HEAD~n 表示 HEAD 前面的第 n 条。这样一来,时间旅行就很方便了,喊出咒语 git checkout HEAD^ 就能查看当前头部的前一条,也就是把最近这一个人放上铁轨之前的状态。

我之前说过,哲学家和上帝或者说科学家的关系很好,他有手段改变历史。这种手段也不难实施,只需要谨慎即可,我现在就传授给你,你只需要喊出咒语 git reset --hard HEAD^,就能把时间拨回上一次改动之前。同一系列的咒语还有 --soft--mixed 等等,但还要等到你更成熟之后,才能施展此法。

严格筛选

哲学家对待他的作品有着近乎偏执的追求,他会像果农挑选樱桃一样挑选受试者。前文提到,哲学家可能还在纠结要不要碾死资本家,他可能发现,这些资本家其中的一些拖欠工资,而另一些则没有。显然,拖欠工资的资本家是一定要死的,但另一部分还需要再作考虑。

此时,哲学家显然不能把整个 foo 分支直接合并到主分支,他需要挑选其中的一些人,并把他们放在 HEAD 后面。这个过程就叫作 cherry-pick

现在,休息吧!

哲学家累了,挑选总是很累的。没关系,我们可以随时退出,之后再继续工作!不过,哲学家需要提醒你的是,千万不要把未经深思熟虑后选择的人合并到主分支,然后就回家睡觉了。一般来说,我们把主分支视作已经确定了的工作,那些没有确定的,就先放在另一个分支上吧,反正也跑不了

一个原则是,一旦有新的想法,就要先创建新分支,然后在分支上添加新人,等工作完成并且确定之后再合并或重整。如你所见,合并和重整并不困难,只需要咒语 git merge 或者 git rebase 就可以了。如果你正在发展一个想法,但是突然需要回到主分支或者另一个分支进行别的工作,也可以用之前学会的 git checkout 咒语进行时间旅行,在时间中移动也是很简单的。

不简单的是改写历史,所以,千万不要把没确定的东西合并到主分支!

在你走之前,还有一些注意事项:如果读完此文发现自己既没有理解电车难题,也没有理解 Git,属于正常现象,前者请求助于道德哲学相关内容,后者可以参考 Learn Git Branching 这个网站。

好了,去睡吧。放心,哲学家是不会偷偷把睡着的人绑在铁轨上的。


  1. Pun intended. ↩︎

  2. 严肃地讲,这并不是 foo 这个词的词源。真正的词源可以参考 RFC 3092 。 ↩︎