🌟 探索Lisp的魅力:全局变量与列表的魔力

在编程的世界里,Lisp就像一位神秘的武术大师,教我们如何在代码与数据之间游刃有余。正如电影《功夫熊猫》中阿宝所学到的那样:“蜡在右手,蜡在左手,蜡在上,蜡在下”,每一个动作都是为了掌握更深的技巧。今天,我们就跟随Lisp的脚步,深入探讨全局变量和列表的奥秘。

🧙‍♂️ Lisp的神奇:代码即数据

Lisp的名字来源于“List Processing”,其核心思想就是通过列表来处理数据。你可以把Lisp视作一个魔法师,它允许你将代码视为数据,反之亦然。这种特性被称为“同构性”(homoiconicity),是其他编程语言所无法企及的。通过这种强大的能力,你将能够以独特的方式操控程序。

⚔️ 恶魔般的全局变量

在这个充满力量的世界中,全局变量就像是双刃剑,既能为你带来便利,也可能让你陷入危险的境地。全局变量的定义如下:

(defvar *symbol-for-global-variable* "optional initial value" "I LOVE DOC strings")

这里的*my-var*是我们定义全局变量的惯例,耳罩样式的星号告诉我们:“小心!我有可能是个危险分子!”为什么说全局变量有“恶魔潜力”呢?因为你可以在任何函数中读取和修改它的值,甚至在REPL中也可以随意操作。这种特性让我们在某些情况下可以轻松实现功能,但在其他情况下却可能导致难以追踪的错误。

🔮 动态作用域

全局变量的另一个特性是“动态作用域”(Dynamic Scope),它就像是历史的奇妙扭曲,让“恶”变成了“善”。动态作用域允许你在函数中访问全局变量,这种设计理念虽然古怪,却为我们提供了强大的灵活性。

📜 列表的力量

在Lisp中,列表是数据的基本形式。你可以通过list操作符来创建列表:

(list 1 2 3 4)

这将返回一个包含四个元素的列表 (1 2 3 4)。但Lisp的魅力不仅于此,它的列表可以包含任何类型的对象:

(list 1 #\a "2" 'symbol-name #'print)

这将返回一个包含数字、字符、字符串、符号和函数的列表 (1 #\a "2" SYMBOL-NAME #<FUNCTION PRINT>)。Lisp的列表就像是一个百宝箱,里面装满了各种各样的宝藏。

🚀 列表操作的多样性

Lisp提供了丰富的操作符,让我们可以对列表进行各种操作。例如,我们可以使用carcdr来获取列表的第一个元素和剩余部分:

(car *mylist*)   ; => 1
(cdr *mylist*)   ; => (2 3 4 5)

这就像是打开一扇窗,透过它我们可以一览无余。更有趣的是,Lisp的列表操作是无副作用的,意味着我们在操作列表时不会改变原始数据。

🎩 赋值与修改

在Lisp中,setf是一个强大的赋值操作符,它可以让你随意修改列表中的元素。比如,想把第二个元素改为99:

(setf (second *mylist*) 99)

这时,*mylist*将变为 (1 99 3 4 5)。就像魔法师挥动魔杖,瞬间改变了世界。

✨ 便捷的列表创建

如果你觉得使用list创建列表太繁琐,可以通过单引号简化操作:

'(1 2 symbol-1 symbol-2 "a string")

这将返回相同的列表,而不需要额外的函数调用。这样一来,编写代码就像是在书写诗篇,优雅而简洁。

🌈 结论:Lisp的无限可能

Lisp以其独特的全局变量和强大的列表操作,为我们提供了无尽的编程可能。无论是定义全局变量,还是操作列表,Lisp都展现出其独特的魅力和灵活性。正如武术的每一个动作都蕴含着深刻的哲理,Lisp的每一行代码也都在诉说着编程的美妙。

在这个充满挑战与机遇的编程世界中,掌握Lisp的技巧就像是获得了无与伦比的力量。让我们继续探索,发现更多编程的乐趣吧!


面向记忆的学习材料

任务目标

快速学习并记住 Common Lisp 中关于全局变量和列表的相关知识

知识点: Lisp 的命名由来
题目: Lisp 的名称源自于哪个短语?
选项:
A. List Processing
B. Logic In Simple Programming
C. Language for Intelligent System Programming
D. Logical Instruction Set Programming

正确答案: A
解析: Lisp 是 “List Processing”(列表处理)的缩写。这个名称反映了 Lisp 语言的核心特性,即它主要用于处理列表数据结构。在 Lisp 中,代码本身也是以列表的形式表示的,这种特性被称为同像性(homoiconicity)。
速记提示: 记住 “List” 这个关键词,Lisp = List Processing。

知识点: Lisp 的同像性
题目: Lisp 语言的哪个特性使得它可以将代码作为数据处理,反之亦然?
选项:
A. 动态作用域
B. 垃圾回收
C. 同像性
D. 函数式编程

正确答案: C
解析: Lisp 的同像性(homoiconicity)是指代码和数据使用相同的表示形式。这使得 Lisp 可以轻松地将代码作为数据处理,或将数据作为代码执行。这是 Lisp 独特而强大的特性,使其在元编程方面具有显著优势。
速记提示: 同像性 = 代码即数据,数据即代码。

知识点: 全局变量的命名约定
题目: 在 Common Lisp 中,全局变量和参数通常使用什么命名约定?
选项:
A. 全部大写
B. 驼峰命名法
C. 下划线分隔
D. 星号包围(earmuffs)

正确答案: D
解析: Common Lisp 中,全局变量和参数通常使用星号(*)包围的命名方式,例如 *my-var。这种命名约定被称为 “earmuffs”(耳罩),目的是提醒开发者这是一个具有潜在”邪恶”(全局影响)的变量。虽然不是所有人都同意这个约定,但它在 Common Lisp 社区中被广泛使用。
速记提示: 全局变量戴”耳罩”(var*)。

知识点: defvar 的特性
题目: 使用 defvar 定义全局变量时,如果变量已存在,重新编译 defvar 语句会发生什么?
选项:
A. 变量值会被更新
B. 变量值保持不变
C. 会抛出错误
D. 变量会被重新创建

正确答案: B
解析: 使用 defvar 定义的全局变量具有特殊性质。如果变量已经存在,重新编译 defvar 语句不会改变变量的现有值。这个特性使得 defvar 适合用于定义那些只需要初始化一次的全局变量。只有在变量首次被引入 Lisp 镜像时,defvar 中的初始值才会生效。
速记提示: defvar 定义的变量,重编译不改值。

知识点: defparameter 的特性
题目: defparameter 与 defvar 在重新编译时的主要区别是什么?
选项:
A. defparameter 不允许重新编译
B. defparameter 每次重新编译都会重新绑定值
C. defparameter 只能用于定义常量
D. defparameter 不支持文档字符串

正确答案: B
解析: defparameter 和 defvar 的主要区别在于,每次重新编译 defparameter 定义时,变量都会被重新绑定到指定的值。这使得 defparameter 更适合用于定义那些可能需要在开发过程中频繁更改的设置或参数。而 defvar 则只在变量首次定义时设置初始值。
速记提示: parameter 参数可变,每次编译都更新。

知识点: defconstant 的用途
题目: 在 Common Lisp 中,defconstant 的主要用途是什么?
选项:
A. 定义可变的全局变量
B. 定义不可变的常量
C. 定义局部变量
D. 定义函数

正确答案: B
解析: defconstant 用于定义不可变的全局常量。一旦使用 defconstant 定义了一个常量,尝试修改它的值将会导致错误。这保证了常量的不可变性。然而,在实际应用中,除了给数值命名外,defconstant 的使用相对较少,更常见的做法是使用 defparameter 来定义”常量”。
速记提示: constant 常量,定义后不可变。

知识点: 列表的创建
题目: 在 Common Lisp 中,下列哪个表达式不能创建列表 (1 2 3 4)?
选项:
A. (list 1 2 3 4)
B. ‘(1 2 3 4)
C. (quote (1 2 3 4))
D. (cons 1 2 3 4)

正确答案: D
解析: 前三个选项都是创建列表 (1 2 3 4) 的有效方式。(list 1 2 3 4) 使用 list 函数创建列表,'(1 2 3 4) 使用引用语法创建列表,(quote (1 2 3 4)) 是 ‘(1 2 3 4) 的完整形式。然而,(cons 1 2 3 4) 是错误的语法,cons 函数只接受两个参数,用于创建点对(dotted pair)或向列表头部添加元素。
速记提示: cons 只接受两个参数,不能直接创建多元素列表。

知识点: car 和 first 函数
题目: 在 Common Lisp 中,car 和 first 函数的区别是什么?
选项:
A. car 用于获取列表的第一个元素,first 用于获取第二个元素
B. car 只能用于点对,first 只能用于列表
C. car 和 first 完全等价,没有区别
D. car 是低级操作,first 是高级抽象

正确答案: C
解析: 在 Common Lisp 中,car 和 first 函数实际上是完全等价的。它们都用于获取列表的第一个元素。car 源自早期 Lisp 实现中的”Contents of Address Register”(地址寄存器内容),而 first 是一个更具描述性的名称。使用哪个主要是风格选择,first 可能更易读,特别是对新手来说。
速记提示: car 和 first 同义,都取列表首元素。

知识点: cdr 和 rest 函数
题目: 对于列表 (1 2 3 4),调用 (cdr list) 会返回什么?
选项:
A. 1
B. (2 3 4)
C. (1 2 3)
D. 4

正确答案: B
解析: cdr 函数(等同于 rest 函数)返回原列表除去第一个元素后的剩余部分。对于列表 (1 2 3 4),调用 (cdr list) 会返回 (2 3 4)。cdr 源自”Contents of Decrement Register”(减量寄存器内容),而 rest 是一个更直观的名称,表示”剩余部分”。
速记提示: cdr/rest 返回去掉首元素后的子列表。

知识点: nth 函数
题目: 对于列表 (a b c d e),表达式 (nth 2 list) 会返回什么?
选项:
A. b
B. c
C. d
E. e

正确答案: B
解析: nth 函数用于获取列表中的第 n 个元素,其中 n 从 0 开始计数。因此,对于列表 (a b c d e),(nth 2 list) 会返回第三个元素,即 c。要注意的是,nth 的计数是从 0 开始的,这与一些其他编程语言中的索引习惯一致。
速记提示: nth 从 0 开始数,2 对应第三个元素。

知识点: append 函数
题目: 表达式 (append ‘(1 2) ‘(3 4)) 的结果是什么?
选项:
A. (1 2 3 4)
B. ((1 2) (3 4))
C. (1 2 (3 4))
D. (3 4 1 2)

正确答案: A
解析: append 函数用于连接两个或多个列表。它会创建一个新的列表,包含所有输入列表的元素,按照输入的顺序依次排列。因此,(append ‘(1 2) ‘(3 4)) 会返回 (1 2 3 4)。需要注意的是,append 不会修改原有的列表,而是创建一个新的列表。
速记提示: append 追加,按序连接多个列表。

知识点: reverse 函数
题目: 对列表 (1 2 3 4) 调用 reverse 函数后,原列表会发生什么变化?
选项:
A. 列表变为 (4 3 2 1)
B. 列表保持不变
C. 列表变为 ((1 2 3 4))
D. 列表变为 (1)

正确答案: B
解析: reverse 函数会返回一个新的、元素顺序相反的列表,但不会修改原始列表。这是因为 Common Lisp 中的大多数列表操作函数都不会产生副作用(side effects)。因此,对列表 (1 2 3 4) 调用 reverse 后,原列表保持不变,而函数返回一个新的列表 (4 3 2 1)。
速记提示: reverse 返回新列表,原列表不变。

知识点: push 函数
题目: 如果 mylist 的值为 (2 3 4),执行 (push 1 mylist) 后,mylist 的新值是什么?
选项:
A. (2 3 4 1)
B. (1 2 3 4)
C. ((1) 2 3 4)
D. (1 (2 3 4))

正确答案: B
解析: push 函数是少数会修改原列表的函数之一。它将一个新元素添加到列表的开头,并更新列表变量。因此,执行 (push 1 mylist) 后,mylist 的新值为 (1 2 3 4)。push 是一个宏,它实际上是使用 cons 和 setf 的组合来实现的。
速记提示: push 推到列表头部,改变原列表。

知识点: pop 函数
题目: 如果 mylist 的值为 (1 2 3 4),执行 (pop mylist) 会返回什么,mylist 的新值又是什么?
选项:
A. 返回 1,mylist 变为 (2 3 4)
B. 返回 (1),mylist 变为 (2 3 4)
C. 返回 4,mylist 变为 (1 2 3)
D. 返回 (1 2 3 4),mylist 变为 nil

正确答案: A
解析: pop 函数会移除并返回列表的第一个元素,同时修改原列表。因此,执行 (pop mylist) 会返回 1,并且 mylist 的新值变为 (2 3 4)。pop 也是一个宏,它结合了 car 和 setf 的功能。
速记提示: pop 弹出并返回首元素,改变原列表。

知识点: 引用语法
题目: 在 Common Lisp 中,'(1 2 symbol) 等价于以下哪个表达式?
选项:
A. (list 1 2 ‘symbol)
B. (quote (1 2 symbol))
C. (cons 1 (cons 2 (cons ‘symbol nil)))
D. (1 2 symbol)

正确答案: B
解析: 在 Common Lisp 中,’是 quote 的简写形式。因此,'(1 2 symbol) 完全等价于 (quote (1 2 symbol))。这种语法用于创建字面量列表,其中的符号不会被求值。这与使用 list 函数创建列表不同,因为 list 函数会对其参数进行求值。
速记提示: 单引号’是 quote 的简写,用于创建字面量列表。

知识点: setf 的通用性
题目: 以下哪个不是 setf 的有效用法?
选项:
A. (setf (car list) 10)
B. (setf (aref array 0) 20)
C. (setf (length list) 5)
D. (setf (gethash key hash-table) value)

正确答案: C
解析: setf 是一个通用的赋值操作符,可以用于多种数据结构。选项 A 用于设置列表的第一个元素,B 用于设置数组的元素,D 用于设置哈希表的值,这些都是有效的用法。然而,C 是无效的,因为 length 是一个只读函数,不能直接设置列表的长度。
速记提示: setf 通用赋值,但不能改变只读属性如 length。

知识点: 列表元素的修改
题目: 如果 mylist 的值为 (1 2 3 4 5),执行 (setf (second mylist) 99) 后,mylist 的新值是什么?
选项:
A. (1 99 3 4 5)
B. (99 2 3 4 5)
C. (1 2 99 4 5)
D. (1 2 3 4 99)

正确答案: A
解析: setf 可以与访问函数如 second 结合使用,来修改列表的特定位置的元素。second 函数返回列表的第二个元素,因此 (setf (second mylist) 99) 会将列表的第二个元素改为 99。执行后,mylist 的新值为 (1 99 3 4 5)。
速记提示: (setf (second list) value) 修改第二个元素。

知识点: 列表的替换
题目: 执行 (setf mylist ‘(1 2 3)) 后,mylist 的值会是什么?
选项:
A. (1 2 3)
B. ((1 2 3))
C. (list 1 2 3)
D. ‘(1 2 3)

正确答案: A
解析: 这个 setf 表达式直接将 mylist 变量的值设置为新的列表 (1 2 3)。'(1 2 3) 创建了一个字面量列表,然后 setf 将这个新列表赋值给 mylist。执行后,mylist 的值就是 (1 2 3)。这展示了如何使用 setf 完全替换一个列表变量的内容。
速记提示: setf 可直接替换整个列表。

总结

本学习材料涵盖了 Common Lisp 中关于全局变量和列表操作的重要概念:

  1. Lisp 的命名由来和同像性特征。
  2. 全局变量的定义方式(defvar, defparameter, defconstant)及其特性。
  3. 列表的创建方法,包括 list 函数和引用语法。
  4. 常用的列表操作函数,如 car/first, cdr/rest, nth, append, reverse 等。
  5. 修改列表的函数,如 push, pop, setf 等。
  6. 引用语法(‘和 quote)的使用。
  7. setf 的通用性和在列表操作中的应用。

这些知识点构成了 Common Lisp 中处理全局变量和列表的基础。掌握这些概念将有助于理解和编写 Lisp 程序,特别是在处理数据结构和进行元编程时。建议读者多加练习这些操作,以熟练掌握 Lisp 的列表处理能力。

参考文献

http://l1sp.org

Common Lisp – The Tutorial Part 6.pdf

https://github.com/rabbibotton/clog/blob/main/LEARN.md


0 0 投票数
Article Rating
订阅评论
提醒
1 评论
最多投票
最新 最旧
内联反馈
查看所有评论
1
0
希望看到您的想法,请您发表评论x