summaryrefslogtreecommitdiff
path: root/content
diff options
context:
space:
mode:
authoreltrac <hi@eltr.ac>2026-05-19 09:51:21 +0800
committereltrac <hi@eltr.ac>2026-05-19 09:51:21 +0800
commitf914d886845727994fbe2d7852d29d305b4032bf (patch)
treeefedb5aa62f6c9528639f11aef18c89cd1c4e4b6 /content
parent80e21bef1499fb7ded8216137c308fa4e59d9228 (diff)
[draft] update weekly draft
Diffstat (limited to 'content')
-rw-r--r--content/weekly/82.md61
1 files changed, 61 insertions, 0 deletions
diff --git a/content/weekly/82.md b/content/weekly/82.md
index 1401a430..f2970b5a 100644
--- a/content/weekly/82.md
+++ b/content/weekly/82.md
@@ -28,6 +28,66 @@ toc = true
就算不相信这样的未来即将到来,「Buy it for life」的思想也是值得学习的。即便消费者文化持续,也不代表所有人都要参与到这种文化当中。
+### Lisp 设计模式
+
+> [!note] 📜
+> [Lisp Design Patterns](https://aartaka.me/lisp-design-patterns.html)
+
+可能是我对设计模式这个概念的理解有偏差(也可能是因为软件领域的用词向来很混乱……),我总觉得这篇文章更像是在介绍如何利用语言特性,而不是在讲设计模式,或者仅仅是编程模式(Programming Patterns)而非设计模式(Design Patterns)。
+
+首先是允许后端成为软件的插件(也就是随时更换具体实现)的 Protocols(协议)和 Monkey Patching(猴子补丁)。Common Lisp 和 Clojure 都支持 Protocol(`defgeneric` 和 `defprotocol`),这和 OOP 语言的 Interface 差不多,可以把面向接口编程的经验复用到一些 Lisp 中。至于 Monkey Patching,是指一些语言允许用户改写已有的函数,比如 Emacs Lisp 的 Adivces 和 Common Lisp 的 `:around` 方法,也就允许用户自己编写并替换软件的部分实现。
+
+然后是 Hooks(钩子),这就更接近设计模式了,把函数存放在一个列表(或别的什么数据结构里),当有事件发生时,就调用这里面的函数。我想起某本 Java 设计模式里也有类似的案例,叫作观察者模式(Oberver Pattern)或监听者模式,被监听对象储存一系列监听者的引用,当事件发生时,由监听对象调用监听者的方法。在大多数 Lisp 里,我们直接储存函数本身,不和对象打交道。
+
+其次是 Fail Fast, Fail Often(快错常错),这更像是开发模式。简而言之,作者表示他在非 Lisp 语言里很害怕看到报错,但在 Lisp 里就不会,因为以 Common Lisp 为首的部分 Lisp 方言有类似的语言特性,可以快速在报错之后快速重启程序,如果遇到暂时不想处理的错误,可以直接忽略,还能使用 `use-value` 这样的特性临时替换有问题的参数。在 Common Lisp 以外的方言,程序员也可以使用 REPL(可以理解为直接与代码库交互的命令行程序)调试程序,在 REPL 里也可以重新定义函数和变量,重新运行程序,非常方便。
+
+再者是 Dynamic Variables(动态变量),看起来很吓人的一个模式,因为它似乎违背了函数式编程范式,给变量赋值了。这是作者给的示例代码:
+
+```lisp
+(defmethod gimage:apropos* :around (string &optional package external-only docs-too)
+ (let ((*print-case* :downcase)
+ (*print-level* 2)
+ (*print-lines* 2)
+ (*print-length* 10))
+ (call-next-method)))
+```
+
+我不懂 Common Lisp,但我熟悉 `let`,这个函数(也可能是宏或者保留字,我不清楚底层是怎么实现的)的第一个参数是是个储存二元关系[^1]的列表,用于声明绑定(Bindings),余下的参数都是待求值的表达式。这里确实是给变量赋值了,但仅限于 `let` 块内,是在限定作用域内改变状态以修改行为的做法。无论怎么说,我还是觉得这不太优雅,为什么一定要用绑定,不能给函数传递参数呢?
+
+最后是 Destructuring(解构),这其实是语言特性,它的作用是把复杂的数据结构分解为扁平的、易用的结构,比如把键值对映射解构为几个简单的变量。作者给的 Guile/Scheme 例子有太多我不熟悉的函数,所以我写个 Clojure 的例子。
+
+```clojure
+(defn http-request [url & {:keys [Accpet Cookie Content-Type]
+ :or {} ;; 默认值
+ :as opts}]
+ ;; 直接使用 Accept、Cookie 和 Content-Type
+ ;; 也可以用 opts,结构是 {:Accept ... :Cookie ... :Content-Type ...}
+ )
+
+(http-request "https://example.com" :Cookie {"key" "value"})
+```
+
+这在构造有很多可选参数的函数时非常有用。如果用户传入的参数本身就是个映射,则可以用 `let` 解构。
+
+```clojure
+(defn http-response [response]
+ (let [{:keys [status headers body]} response]
+ ;; 直接使用 response map 里的 status、headers 和 body
+ ))
+```
+
+不过这也不是 Lisp 专属的,因为 JavaScript 也可以解构。
+
+```js
+const { status, headers, body } = response
+
+[a, b, ...rest] = [10, 20, 30, 40, 50];
+console.log(rest);
+// Expected output: Array [30, 40, 50]
+```
+
+印象中 Python 也可以,作者甚至把解构称作「Pythonique」。
+
---
## 止语
@@ -44,3 +104,4 @@ toc = true
## 切片
+[^1]: 死去的离散数学突然开始攻击我…… 这里说的二元关系就是只储存了两个元素的列表,实际上是键值对。