为了把工作从以市场为中心的计算和限制中解放出来,就必须以工艺伦理(ethics of workmanship)取代劳动力市场发展过程中形成的工作伦理。托斯丹·凡勃伦早就指明,“工艺本能”有别于工作伦理这个现代发明,是人类的自然倾向。人是具有创造力的生物。如果认为标价牌是区分工作与非工作、努力与懒惰的标准,那是对人类本性的贬低;如果认为没有收益,人们宁愿闲着,让自己的技能和想象力腐烂生锈,那是对人类本性的肢解。工艺伦理将恢复人类本能的尊严,恢复社会公认的意义。现代资本主义社会形成且根深蒂固的工作伦理却否认了这种尊严和意义。
If you find yourself fretting about “shoulds” or “to-dos,” find the nearest cafe and order another coffee and stay exactly where you are. You are not being lazy, you are investing in the leisure. In the mental space where new ideas happen to wander in, wearing a linen shirt and asking if this seat is taken.
You might be reading, again. Maybe you’re watching someone water a balcony garden, or listening to a conversation in a language you barely understand. Maybe you’re simply enjoying the pleasant fact that no one needs you for anything right now.
文章简单介绍了配置文件格式的历史,从一开始不规范且扁平的 INI 文件,再到能够表达层级但过于臃肿的 XML,之后是适合数据交换但对人类编辑不够友好的 JSON,最终是这篇文章的主角——YAML 和 TOML。
顺带一提,我永远不会错过批评 Java 的机会:
Anyone who maintained a Java web.xml or an Ant build file in 2003 knows what it was like to edit dozens of nested elements just to change a database connection string. The verbosity made the files difficult to maintain by hand, which is precisely what configuration files demand.
任何在 2003 年维护过 Java web.xml 或者 Ant 构建文件的人都知道,编辑一大堆嵌套的元素,只为了修改数据库连接字符串是什么样的。文件冗长使其很难徒手维护,而这恰恰是配置文件需要的。
如今 Spring Boot 简化了很多配置,但 Java 生态依旧使用 XML 格式,还有另一个令人闻风丧胆的 properties 文件,可以说是完美融合了 YAML 和 TOML 两边的缺点。
TOML is a bad file format. It looks good at first glance, and for really really trivial things it is probably good. But once I started using it and the configuration schema became more complex, I found the syntax ugly and hard to read.
十九岁生日,是和我在大学认识的朋友一起(没有一个是和我同一个学校、同一个班级的,我现在仍然和同班同学走得很远)。我不记得我们干了些什么,我只记得最后我们五六个人在私人影院看恐怖片。那是在我和
M
表过白之后的事情,我们决定做朋友,但我不能否认我对他还有情感(甚至现在也是)。我记得那天他先走了,因为他的学校有宵禁。之后他跟我发消息,说他在跑回寝室的路上摔了一跤,我们之后又聊了聊。和其他人发生的其他事情,我都忘了。
再往上走是塔坪,还有各种开在老居民楼下的小店,我要去的那几家基本都开在那里。下一个目的地是名叫 Boom Cup 的杯子店,貌似也挺火,不过我没有找到什么心仪的杯子。或许我对杯子的审美和我对纸品的审美趋同,和大众喜欢的相差甚远吧。离开之后我有些迷路,便在旁边随便转了转,发现了一些小店,门面都挺好看,还有一家文创店门口全是绿叶。绕了好久才找到下一个目的地,也是我此行最满意的一家手帐用品店,名字叫作夏眠商店。
The Rust community doesn’t want beginners to learn Rust — it wants advanced programmers to learn Rust, which could even be fatal for the language in the long run.
回到编程语言的文档这个话题。作者用《PHP: The Complete Reference》和 Rust 的官方文档对比,我其实觉得不太公平,前者也不是 PHP 的官方文档啊。Rust 文档的开头其实就把目的明确了。
This book assumes that you’ve written code in another programming language, but it doesn’t make any assumptions about which one. We’ve tried to make the material broadly accessible to those from a wide variety of programming backgrounds. We don’t spend a lot of time talking about what programming is or how to think about it. If you’re entirely new to programming, you would be better served by reading a book that specifically provides an introduction to programming.
如果只是出门走走,我会觉得有些浪费时间,但如果是锻炼身体的话,就是一举两得了。所以某一天我设置了七点的闹钟,尽管前一天晚上我一点多才睡,但我还是强制开机,让自己爬起来了。强制开机并不痛苦,关键在于醒来的时机,如果刚好在快速眼动睡眠期(REM)醒来,就会感到神清气爽,即便总睡眠时长可能只有四五个小时。这是因为 REM 是睡眠周期的「交界点」,是前一个周期开始、后一个周期结束的阶段。
我记得 REM 过后,人本身就会醒来,一个晚上人会从睡梦中醒来几次,只不过忘记了而已。夜间醒来,哪怕是有意识地去做了某些事情,也很容易被忘记。我记得有一次我在半夜突然流鼻血,醒来之后察觉异样,看到手上全是血,摸黑找纸巾收拾之后我很快就睡了。第二天我在洗漱时,从嘴里吐出了带血丝的痰液,疑惑片刻后才想起前一天晚上的事情。
这个观点可能不新了,在 Peter Norvig 的
Design Patterns in Dynamic Languages
(动态语言中的设计模式)演讲中,他提到在包括 Common Lisp 在内的多种动态语言里,23 中设计模式中的 16 种要么消失了,要么被简化——我得提醒读者,这些设计模式里还有
解释器模式
,用于嵌入脚本语言,属于和架构关联较弱的模式,这是剩下的 7 种之一。
最近在玩
Common Lisp
,由于中文博客几乎没有讨论,包括文档在内的英文资料都很零散——官网上只有简单的上手介绍,而社区维护的
文档
的某一页竟然写着 Please help us fill this page,我想这是因为 CL 的实现太多了,相关的网站很多,人力是分散的——看来得自力更生了,也借此写一篇博客,既用作信息整理,也试图提高一些 Lisp 的讨论度吧。
尽管 Common Lisp 的具体实现之间差异不会太大,但我想还是说明一下比较好:我使用的 Common Lisp 实现是
Steel Bank Common Lisp
,即 sbcl。
一个有序对的前半部分被称作 car,后半部分被称作 cdr,(car (cons x y)) 返回 x,(cdr (cons x y)) 返回 y。这两个词的意思分别是寄存器编号的地址部分的内容(Contents of the Address part of the Register Number)和寄存器编号的减量部分的内容(Contents of the Decrement part of the Register Number)。之所以这么命名,貌似是出于一些历史原因,最早的 Lisp 是在上世纪的 IBM 704 计算机上实现的,和硬件强相关。不过具体的背景我就不打算深究了,这不是词源学系列。
其中有相当一部分“函数”会直接绑定到某个 Go 语言函数上,另外一些被定义为「特殊形式」,会做特殊处理。不过我在这里做了一个不太明智的决策:由于这是个多维表格应用,所以我内置了一些快速获取表格数据的「特殊形式」,比如 (where this (= id 1)) 能够获取 id 为 1 的行,其中 (= id 1) 创建了新的上下文,id 被绑定到表格的列名上,并且每次遍历都会执行一遍,这显然不是个函数,因为 (= id 1) 若是直接求值就会报错。如果要用函数实现,就要传入一个用于筛选的匿名函数,比如这样 (where this #(= (get % "id") 1))4——这更符合直觉,但语法又变得复杂了。
然而编写特例又会让语言本身变得臃肿,所以另一种做法是编写宏(macro),也就是能够写代码的代码。既然 (where this #(= (get % "id") 1)) 可以用函数实现,不必编写特例,而 (where this (= id 1)) 更简洁,那我能不能编写宏,让 (where* this (= id 1))5 在解释(或编译)之前就被展开为 (where this #(= (get % "id") 1)) 呢?
let 显然不是函数,而是特殊形式,因为它创建了一个变量作用域,还包含未声明的符号,这些符号若是被当作函数参数求值就会报错。不过,它底下到底是怎么实现的呢?和其他语言的 let 变量声明一样吗?
在《Let Over Lambda》一书中,作者这样写道:
Although what let does is unambiguous, how it does it is deliberately left unspecified. What let does is separated from how it does it. Somehow, let needs to provide a data structure for storing pointers to values.
尽管 let 的作用并不模糊,它发挥作用的方式却被刻意地留白了。let 的作用与它的作用方式分离。通过某种方式,let 需要提供用来储存指向值的指针的数据结构。
The first part of the let expression is a list of name-expression pairs. When the let is evaluated, each name is associated with the value of the corresponding expression. The body of the let is evaluated with these names bound as local variables. The way this happens is that the let expression is interpreted as an alternate syntax for
No new mechanism is required in the interpreter in order to provide local variables. A let expression is simply syntactic sugar for the underlying lambda application.
这段代码首先使用 lambda 创造了一个匿名函数,这个函数有 n 个形参,同时,这段代码直接调用了这个函数,传入了 n 个参数。对 Lisp 不熟悉的读者(真的会有那样的读者读到这里吗?)可以参考 JavaScript 的立即执行函数。
(function(a, b) {
return a + b
})(1, 2);
// => 3
这相当于:
((lambda (a b) (+ a b)) 12)
;; 相当于(let ((a1) (b2))
(+ a b))
要注意的是,SICP 使用的是 Scheme 而非 Common Lisp,不过你也看到了,这两门语言其实有不少相似之处。区别之一当然就是,Common Lisp 不指定 let 的实现方式,编译器可以做各种优化;而 Scheme 中,let 就是 lambda 的语法糖。
后者当然是更函数式的——有什么比创建一个立即执行的匿名函数以实现局部变量更函数式的?从效率方面考虑的话,调用函数就要在内存中创建函数栈帧,而经过优化的 Common Lisp 程序可以把数据直接存放在 CPU 寄存器里,这在执行效率上是更好的。
改成 Never Register、Register Later 和 Register Now 能解决这个问题,但按钮看起来就不会这么整洁了。在我看来,丢掉多余的 Register 也能避免语义冗余,字太多不但会造成视觉负担,还会造成理解负担。
Jim Nielsen 对此有些思考,他把 Avoid “Click Here” 规则定义为「系统规则」,把 ClarisWorks 看似违反规则的做法当作最适合当前软件的「本地化方案」。他感叹,如今的软件为了扩大规模,貌似已经完全用成熟的「系统规则」代替「本地化方案了」,因为前者又快又足够好,费心思设计提示框的文字不仅费时费力,还不能带来显著的回报。
As software moves towards “scale”, I can’t help but think that systematic rules swallow all decision making because localized exceptions become points of friction — “We can’t require an experienced human give thought and care to the design of every single dialog box.”
我想补充的是,不久前 Bitwarden CEO 换人,引起了不少
风波
。尽管目前看起来安好,但貌似已经有一些发疯的前兆了。我决定脱离 Bitwarden 生态。
我是前不久才自托管 Vaultwarden 的,当时只是想要快点找到一个 Apple Passwords 的替代。一方面是为了完全自己掌控数据,另一方面是长远考虑,我之后可能会半脚踏出苹果生态,去整一台 Linux PC(不过近期还不打算折腾)。Docker 镜像跑起来的时候我就心生疑惑:这东西跑在公网上真的安全吗?密码库有必要二十四小时在线吗?
holla 和 hollo 最开始被用于引起注意,就像古撒克逊语里招呼船夫用的 halo! 一样。人们见面时打招呼会说 Good day!、How do you do? 或者 How are you,不会说 hello。就像法语里的招呼语 Bonjour 其实也是 Good day 的意思,「嗨」「哈喽」本身就是没有实义的“声响”。
顺带一提,还有一个偏古典文雅的招呼语叫作 Hail,这个词也可以作为动词表示招呼某人。前不久很火的《挽救计划》的英文原名是 Project Hail Mary,其中 Hail Mary 指的是向圣母马利亚祈祷的经文。还有 all hail(全体致敬)这个词,表示更热烈的问号和欢迎。
出于某些原因,Hail 还表示「下冰雹」,但再细究下去这篇文章就写不完了。
Allô
古时候向来是英语向法语借词,justice、government 和 liberty 等概念无一例外都是法语词,更详细的内容我已经在《
为什么说英语是语言界的最大缝合怪?
》讨论过了。如今法语也在不断从英语借词,尤其是 IT 词汇,像购物网站的愿望单一词就直接使用了英语 wishlist 而非 liste de souhaits。
然而,当我在思考法语的 allô 究竟是个什么东西的时候,我怎么也没想到——它就是 Hello 的法语写法啊!法语的 H 大多时候都是哑音。
They’ll talk to you as if they’re human, except all of their words are written by an LLM. Anything you tell them, they feed to the same LLM and send you the response.
他们与你交谈,就好像真人一样,不过他们的话全是 LLM 写的。你告诉他们的任何话,他们喂给同一个 LLM,再把回复发给你。
Effectively, you end up talking to the LLM via a meat proxy.
实际上,你是在通过人肉代理跟 LLM 说话。
Technical taste is different from technical skill. You can be technically strong but have bad taste, or technically weak with good taste. Like taste in general, technical taste sometimes runs ahead of your ability: just like you can tell good food from bad without being able to cook, you can know what kind of software you like before you’ve got the ability to build it. You can develop technical ability by study and repetition, but good taste is developed in a more mysterious way.