diff options
| author | eltrac <hi@eltr.ac> | 2026-05-19 09:51:21 +0800 |
|---|---|---|
| committer | eltrac <hi@eltr.ac> | 2026-05-19 09:51:21 +0800 |
| commit | f914d886845727994fbe2d7852d29d305b4032bf (patch) | |
| tree | efedb5aa62f6c9528639f11aef18c89cd1c4e4b6 | |
| parent | 80e21bef1499fb7ded8216137c308fa4e59d9228 (diff) | |
[draft] update weekly draft
| -rw-r--r-- | content/weekly/82.md | 61 |
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]: 死去的离散数学突然开始攻击我…… 这里说的二元关系就是只储存了两个元素的列表,实际上是键值对。 |
