分类: 软件

  • 🌪️ 旋转与循环:在Lisp的世界里翩翩起舞

    在这个充满无限可能的编程宇宙里,Lisp就像是一位古老的舞者,以优雅的身姿在功能与数据之间自由穿梭。今天,我们将深入探讨Lisp中的闭包、循环和字符串处理,带你领略这位舞者如何在代码的舞台上翩翩起舞。

    🌍 生命的循环:闭包与作用域

    生活中,循环往往让人感到乏味,但在Lisp的元宇宙里,循环则是行动的象征,而函数则是通往内心世界的窗口。首先,我们要理解作用域的概念——就如一位演员在舞台上,只能在特定的时间和空间内展现其才华。

    全局符号的作用域使得Lisp图像的任何部分都可以达到或修改其值,而其他符号则拥有词法作用域。这意味着这些符号只在被定义的文本块中有效,当文本块结束时,它们不再与原值绑定。比如,参数符号只在函数体内与调用函数的参数绑定,直到函数返回为止。以下是一个简单的示例:

    (defun my-func (param1 param2)
      (let ((sum (+ param1 param2)))
        (print sum)))

    在这个函数中,param1param2的作用域是整个函数体,而sum的作用域则只限于let块内部。这就像一位舞者在舞台上,随着音乐的节奏,只有在特定的旋律中才能展现自己的风采。

    🔍 闭包:锁住环境的魔法

    在Lisp中,我们可以定义局部符号,使用letlet*来实现。这就像是在舞台上为每位演员准备了自己的道具,确保他们在表演时能够随心所欲。更神奇的是,Lisp允许我们返回一个函数,形成闭包,这样即使源作用域已经结束,定义时的环境依然与函数相伴而生。

    看看这个“工厂”函数,它为我们生产加法器:

    (defun my-adder (grow-by)
      (let ((sum 0))
        (lambda ()
          (incf sum grow-by))))

    在这个例子中,my-adder函数返回一个闭包,能够记住它的环境,保持sum的状态。你可以创建多个加法器,每个加法器都拥有自己的“记忆”:

    (defvar *two-counter* (my-adder 2))
    (defvar *three-counter* (my-adder 3))
    
    (funcall *two-counter*) ; => 2
    (funcall *two-counter*) ; => 4
    (funcall *three-counter*) ; => 3

    这就像是每位舞者都有自己的节奏与风格,尽管在同一个舞台上表演,却展现出不同的魅力。

    🔄 循环:编程世界的舞步

    在Lisp中,控制流的另一大类便是循环。我们可以使用多种形式的循环,从简单的loop到更复杂的dotimesdolist。想象一下,舞者在舞台上如何随着音乐的节拍反复舞动。

    🎶 永恒的循环

    在Lisp中,loop可以形成一个永恒的循环,直到调用return

    (let ((n 0))
      (loop
        (princ ".")
        (if (> n 10)
            (return n)
            (incf n))))

    这段代码就像是在舞台上不停地转圈,直到你决定停下来的那一刻。

    🔢 固定次数的循环

    dotimes允许我们重复执行某个动作固定次数:

    (dotimes (n 10)
      (princ "."))

    想象一下,舞者在舞台上跳了十次相同的舞步,观众们不禁为之喝彩。

    📜 遍历的循环

    dolist则是将每个元素一一展现,如同舞者在每个观众面前展示各自的风采:

    (dolist (n '(1 2 3 4 5))
      (princ n))

    在这里,每一个数字都是一个舞者,依次走上舞台,展现他们的魅力。

    🧵 字符串:编程中的语言之舞

    在Lisp中,字符串用双引号括起来,形成一个字符的一维数组。Lisp提供了多种字符串操作符,帮助我们在字符的世界中自由舞动。

    字符串的魔法

    常见的字符串操作包括:

    • (length x):获取字符串长度
    • (string= x y):比较两个字符串的相等性
    • (string-upcase x):将字符串转换为大写
    • (reverse x):反转字符串内容

    这些操作就像是舞者在舞台上的各种姿态,灵活多变,令人目不暇接。

    🎭 字符与字符串的转换

    在Lisp中,字符用#\表示,字符常量也可以用类似的方式定义:

    #\space ; 表示空格字符

    通过这些表达,我们可以在字符串与字符之间自由转换,仿佛舞者能够在不同的舞蹈风格之间自如切换。

    📚 结论:在Lisp的舞台上畅游

    在Lisp的世界中,闭包、循环与字符串处理如同舞蹈的各个元素,彼此交织,形成了一幅绚丽的编程画卷。每一个功能都是舞者的舞步,每一行代码都是他们的旋律。在这个舞台上,我们不仅能看到代码的美丽,更能感受到编程的乐趣。

    📝 参考文献

    1. Common Lisp – The Tutorial Part 7
    2. https://github.com/rabbibotton/clog/blob/main/LEARN.md

    面向记忆的学习材料

    任务目标

    快速学习并记住Common Lisp中关于闭包、循环和字符串处理的重要概念。

    知识点: 作用域和闭包
    题目: 在Common Lisp中,以下哪种符号具有词法作用域(lexical scope)?
    选项:
    A. 全局符号
    B. 函数参数
    C. 特殊变量
    D. 宏参数

    正确答案: B
    解析: 在Common Lisp中,全局符号具有全局作用域,可以在整个Lisp镜像中访问或修改。而函数参数等其他符号具有词法作用域,仅在其定义的文本块内有效。函数参数的绑定仅在函数体内有效,直到函数返回。
    速记提示: “函数参数,局部有效”

    知识点: let和let*的使用

    题目: 以下哪种情况下应该使用let*而不是let?
    选项:
    A. 当需要定义多个相互独立的局部变量时
    B. 当一个定义需要引用另一个定义时
    C. 当只需要定义一个局部变量时
    D. 当定义的变量都是全局的时

    正确答案: B
    解析: let用于创建局部变量,但如果一个定义需要引用另一个定义,就应该使用let。let*允许后面的定义引用前面的定义,而let中的所有定义是并行的,不能相互引用。
    速记提示: “let*允许相互引用”

    知识点: 闭包的概念
    题目: 在Common Lisp中,什么是闭包(closure)?
    选项:
    A. 一种特殊的全局变量
    B. 一种循环结构
    C. 一个带有其定义环境的函数
    D. 一种字符串处理方法

    正确答案: C
    解析: 闭包是一个函数,它携带了其定义时的环境。即使函数的源代码范围已经词法关闭(最后的右括号已闭合),定义该函数的环境仍然与之一起存在。这就是为什么它被称为”闭包”。
    速记提示: “函数+环境=闭包”

    知识点: loop..return循环
    题目: 在Common Lisp中,如果loop..return结构中从未调用return,会发生什么?
    选项:
    A. 循环会立即终止
    B. 循环会执行一次后终止
    C. 循环会无限执行
    D. 会产生编译错误

    正确答案: C
    解析: 在Common Lisp中,如果loop..return结构中从未调用return,循环将永远执行下去。return用于提供退出循环的条件,如果没有这个条件,循环就会一直进行。
    速记提示: “无return,永远loop”

    知识点: dotimes循环
    题目: 在Common Lisp的dotimes循环中,循环变量n的取值范围是什么?
    选项:
    A. 1 到 n
    B. 0 到 n
    C. 0 到 n-1
    D. 1 到 n-1

    正确答案: C
    解析: 在dotimes循环中,循环变量n的取值范围是0到n-1。循环重复n次,每次循环n等于0, 1, 2, …, 直到n-1。
    速记提示: “dotimes从0到n-1”

    知识点: dolist循环
    题目: 在Common Lisp的dolist循环中,循环变量n是如何变化的?
    选项:
    A. n每次增加1
    B. n每次减少1
    C. n绑定到列表的下一个元素
    D. n保持不变

    正确答案: C
    解析: 在dolist循环中,每次循环n都会绑定到列表的下一个元素,直到所有元素都被处理完毕。这使得dolist非常适合遍历列表中的所有元素。
    速记提示: “dolist绑定下一元素”

    知识点: do循环
    题目: Common Lisp中的do循环有什么特点?
    选项:
    A. 只能进行单一循环
    B. 只能处理数字
    C. 允许同时进行多个循环
    D. 必须使用关键字loop

    正确答案: C
    解析: Common Lisp中的do循环允许同时进行多个循环。每个循环都可以定义为(符号 起始值 如何改变符号)的形式。这使得do循环非常灵活,能够处理复杂的循环逻辑。
    速记提示: “do支持多循环并行”

    知识点: 字符串创建
    题目: 在Common Lisp中,如何创建字符串?
    选项:
    A. 使用单引号
    B. 使用双引号
    C. 使用反引号
    D. 使用括号

    正确答案: B
    解析: 在Common Lisp中,字符串是使用双引号创建的。例如,”Hello, World!”就是一个字符串。字符串在Lisp中被视为字符的一维数组。
    速记提示: “双引号创建字符串”

    知识点: 字符串操作
    题目: 以下哪个函数用于获取字符串的长度?
    选项:
    A. string-length
    B. str-len
    C. length
    D. size

    正确答案: C
    解析: 在Common Lisp中,length函数用于获取字符串的长度。这个函数不仅可以用于字符串,还可以用于其他序列类型。使用方法为(length x),其中x是要测量长度的字符串。
    速记提示: “length测长度”

    知识点: 字符串比较
    题目: Common Lisp中用于比较字符串相等性的函数是?
    选项:
    A. string=
    B. equal
    C. eq
    D. eql

    正确答案: A
    解析: 在Common Lisp中,string=函数用于比较两个字符串是否相等。这是一个区分大小写的比较。使用方法为(string= x y),其中x和y是要比较的字符串。
    速记提示: “string=比较字符串”

    知识点: 字符串大小写转换
    题目: 如何将字符串转换为大写?
    选项:
    A. string-uppercase
    B. string-upcase
    C. upcase-string
    D. to-upper

    正确答案: B
    解析: 在Common Lisp中,string-upcase函数用于将字符串转换为大写。它返回一个新的大写字符串,而不修改原始字符串。使用方法为(string-upcase x),其中x是要转换的字符串。
    速记提示: “string-upcase变大写”

    知识点: 字符串修剪
    题目: 哪个函数用于去除字符串两端的空白字符?
    选项:
    A. string-trim
    B. string-strip
    C. trim-string
    D. remove-whitespace

    正确答案: A
    解析: 在Common Lisp中,string-trim函数用于去除字符串两端的空白字符。它返回一个新的已修剪的字符串。使用方法为(string-trim ” ” x),其中x是要修剪的字符串,第一个参数指定要修剪的字符(这里是空格)。
    速记提示: “string-trim去两端”

    知识点: 字符串连接
    题目: 在Common Lisp中,如何连接多个字符串?
    选项:
    A. string-append
    B. concat
    C. concatenate
    D. join

    正确答案: C
    解析: 在Common Lisp中,concatenate函数用于连接多个字符串。使用方法为(concatenate ‘string x y),其中x和y是要连接的字符串。注意第一个参数’string指定了结果的类型。
    速记提示: “concatenate连接字符串”

    知识点: 字符到字符串的转换
    题目: 如何将一个字符转换为字符串?
    选项:
    A. char-to-string
    B. make-string
    C. string
    D. to-string

    正确答案: C
    解析: 在Common Lisp中,string函数用于将一个字符转换为字符串。使用方法为(string #\x),其中#\x表示要转换的字符。这个函数会返回一个包含该字符的新字符串。
    速记提示: “string函数字符变字符串”

    知识点: 字符串解析为数字
    题目: 哪个函数用于将表示整数的字符串转换为数字?
    选项:
    A. string-to-number
    B. parse-integer
    C. read-number
    D. to-integer

    正确答案: B
    解析: 在Common Lisp中,parse-integer函数用于将表示整数的字符串转换为数字。使用方法为(parse-integer “123”),它会返回对应的整数值。这个函数只能处理整数,不能处理浮点数。
    速记提示: “parse-integer字符串变整数”

    知识点: 数据到字符串的转换
    题目: 如何将任意Lisp对象转换为其打印表示的字符串?
    选项:
    A. object-to-string
    B. print-to-string
    C. write-to-string
    D. stringify

    正确答案: C
    解析: 在Common Lisp中,write-to-string函数用于将任意Lisp对象转换为其打印表示的字符串。使用方法为(write-to-string x),其中x是要转换的对象。这个函数会返回一个新的字符串,表示该对象的打印形式。
    速记提示: “write-to-string对象变字符串”

    知识点: 字符表示
    题目: 在Common Lisp中,如何表示一个字符?
    选项:
    A. ‘c
    B. “c”
    C. #\c
    D. \c

    正确答案: C
    解析: 在Common Lisp中,字符使用#\加上字符本身来表示。例如,#\z表示字符’z’。这种表示方法允许明确地指定单个字符,而不是字符串或符号。
    速记提示: “#\表示单个字符”

    知识点: 字符操作
    题目: 哪个函数用于获取字符串中特定位置的字符?
    选项:
    A. string-char
    B. char
    C. get-char
    D. elt

    正确答案: B
    解析: 在Common Lisp中,char函数用于获取字符串中特定位置的字符。使用方法为(char my-string x),其中my-string是字符串,x是要获取的字符的索引(从0开始)。这个函数返回指定位置的字符。
    速记提示: “char获取特定字符”

    知识点: 字符比较
    题目: 用于比较两个字符是否相等的函数是?
    选项:
    A. char-equal
    B. char=
    C. equal-char
    D. eq

    正确答案: B
    解析: 在Common Lisp中,char=函数用于比较两个字符是否相等。这是一个区分大小写的比较。使用方法为(char= x y),其中x和y是要比较的字符。如果两个字符相等,函数返回true。
    速记提示: “char=比较字符相等”

    知识点: 字符常量
    题目: 以下哪个不是Common Lisp中预定义的字符常量?
    选项:
    A. #\space
    B. #\newline
    C. #\tab
    D. #\comma

    正确答案: D
    解析: Common Lisp预定义了一些特殊的字符常量,包括#\space(空格)、#\newline(换行)、#\tab(制表符)等。但#\comma(逗号)不是预定义的字符常量。其他常用的字符常量还包括#\return(回车)、#\backspace(退格)等。
    速记提示: “常用特殊字符有预定义常量”

    总结

    本学习材料涵盖了Common Lisp中关于闭包、循环和字符串处理的重要概念。主要内容包括:

    1. 作用域和闭包:理解全局作用域和词法作用域的区别,以及闭包的概念和应用。
    2. 循环结构:介绍了loop..return、dotimes、dolist和do等不同类型的循环,每种循环都有其特定的用途和语法。
    3. 字符串操作:包括字符串的创建、比较、修改、连接等基本操作,以及字符串与其他数据类型之间的转换。
    4. 字符处理:讲解了字符的表示方法,以及相关的操作函数。

    掌握这些概念对于使用Common Lisp进行编程至关重要。建议读者多加练习,特别是在实际编程中运用这些概念,以加深理解。


  • 🌟 探索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


  • 控制流的艺术:用Lisp编织逻辑的甜甜圈🍩

    在编程的宇宙里,控制流就像一条蜿蜒的河流,时而平静,时而汹涌。今天,我们将深入探讨Lisp语言中的控制流,犹如在制作美味的甜甜圈,层层叠叠,充满惊喜。就让我们在这次旅程中,解锁控制流的秘密,探索如何在代码中创造出灵动的逻辑吧!

    🛠️ 结构的搭建:Lisp程序的框架

    在我们进入控制流的具体细节之前,首先得构建一个坚实的基础。Lisp程序的外部结构可以比作甜甜圈的外壳,而内部包(packages)则是甜甜圈的馅料。Lisp的程序结构并不是自上而下或自下而上的,而是一种有机的、环环相扣的方式。就像制作甜甜圈时,准备好面团后,才是时候让它们成形。

    🎢 生命周期:从REPL到函数调用

    让我们先回顾一下在REPL环境下调用函数的过程。以函数give-it为例,当我们在REPL中输入(give-it 1)时,控制流便从REPL转向了函数,此时参数here被绑定为1。接下来,函数体内的每一个表达式都像甜甜圈里的果酱,逐一被处理,最后返回的值则是这个函数的核心——正如一个完美的甜甜圈,中心的果酱才是最诱人的部分。

    (defun give-it (here)
      (princ here)
      (print here))

    通过这个例子,我们可以清晰地看到控制流是如何在函数内进行的。它不仅仅是简单的参数传递,更是一个复杂而美妙的过程,涵盖了从参数绑定到返回值的每一个步骤。

    🌊 流动的美妙:函数体内的控制流

    在函数体内,控制流的运作尤为重要。Lisp提供了多种“块”形式,例如progn,它允许我们在一个块中执行多个表达式,但只返回最后一个表达式的值。就像在甜甜圈制作过程中,虽然我们可以尝试不同的口味,但最终的成品只有一个。

    🔄 使用progn的艺术

    progn的主要用途是允许在控制流的中间执行副作用,比如打印文本或更新状态。在我们修改的hello-private函数中,我们可以看到如何将副作用与赋值操作结合起来。

    (defun hello-private (body)
      "Create a div on new pages containing - hello world
       That when clicked changes color."
      (set-on-click (create-div body :content "hello world")
        (lambda (obj)
          (setf (color obj) (progn
                              (terpri)
                              (princ "Setting color to:")
                              (print (rgb (random 255)
                                           (random 255)
                                           (random 255))))))))

    在这个例子中,当用户点击DIV时,程序不仅改变了颜色,还在终端中打印了相关信息,这就是Lisp中的一种流动之美。

    🎮 游戏中的控制流:从ifcond

    控制流并不止于简单的条件判断。借助ifcond,我们可以构建更复杂的逻辑结构。if允许我们在两种情况之间进行选择,而cond则可以处理多种条件,就像在甜甜圈上撒上不同风味的糖霜,丰富了整体的口感。

    🔍 细节分析:cond的结构

    让我们看看如何使用cond构建一个小游戏,增加交互性:

    (defun hello-private (body)
      (set-on-click (create-div body :content "CLICK ME TO PLAY")
        (lambda (obj)
          (setf (color obj) (cond ((equal (random 10) 1)
                                   (setf (text obj) "--(O. -(O)--")
                                   (print "RED LIGHT!")
                                   (rgb 255 0 0))
                                  (t
                                   (setf (text obj) "--(X. -(X)--")
                                   (print "I'm not looking..")
                                   (rgb 0 (random 255) (random 255))))))))

    在这个例子中,cond允许我们根据不同的随机数来改变DIV的文本和颜色,增强了用户的参与感。每一次点击都是一个新的体验,就像每一口甜甜圈都带来不同的惊喜。

    🧩 比较与选择:equalequalp

    在Lisp中,比较是控制流的重要组成部分。通过equalequalp,我们可以对不同类型的数据进行比较,选择最适合的逻辑路径。就如同在选择甜甜圈的口味时,我们需要考虑个人的偏好。

    ⚖️ 深入比较:eqeql

    除了equalequalp,Lisp还提供了eqeql,用于内存中对象的比较。这为我们提供了更为精确的控制能力,特别是在处理符号和列表时。通过这些比较操作,我们可以确保在复杂的控制流中做出正确的选择。

    📜 总结:流动的控制与甜蜜的创造

    通过对Lisp控制流的理解,我们能够更灵活地构建程序逻辑,实现复杂的功能。控制流不仅仅是代码的执行顺序,更是创造力的体现。就像制作甜甜圈一样,每个步骤都至关重要,最终成品的口感与美感也依赖于我们的每一个选择。


    terpri 是 Common Lisp 中的一个函数,它的功能是向标准输出流发送一个换行符。可以把它理解为在打印文本时,强制光标移动到下一行的操作。这个函数特别适用于需要在输出中添加换行的场景。

    📜 terpri 函数的详细解析

    1. 基本用法

    在使用 terpri 时,我们通常不需要传递任何参数。它的调用形式很简单:

    (terpri)

    当你在 REPL 或者程序中调用这个函数时,它会在标准输出流中插入一个换行符。换句话说,使用 terpri 就像在你写的文章中按下了“回车”键,让光标跳到下一行。

    2. 与其他输出函数的结合

    在打印多个输出时,terpri 常常与其他输出函数结合使用。例如,假设你想打印一些文本并在每条文本之间添加换行,可以这样写:

    (princ "Hello, World!")
    (terpri)
    (princ "Welcome to Lisp!")

    这段代码的输出将会是:

    Hello, World!
    Welcome to Lisp!

    正如你所见,terpri 使得文本输出更加整齐,与后续的输出内容分隔开来。

    3. 使用场景

    • 输出格式化:在需要格式化输出时,例如在生成报告或日志信息时,使用 terpri 可以让输出更具可读性。
    • 交互式程序:在用户与程序交互时,清晰的输出和换行可以帮助用户更好地理解程序的状态和提示信息。

    4. 示例

    下面是一个更完整的示例,展示了如何在一个输出操作中使用 terpri

    (defun print-greetings ()
      (princ "Good morning!")
      (terpri)
      (princ "Hope you have a great day!")
      (terpri)
      (princ "Goodbye!"))
    
    (print-greetings)

    运行这段代码后,输出将会是:

    Good morning!
    Hope you have a great day!
    Goodbye!

    总结

    terpri 函数是一个简单但非常实用的工具,能够帮助我们在输出中添加换行符,从而使输出更加整齐和易读。在编写程序时,合理使用 terpri 可以大大提升用户体验,特别是在多行输出的场景中。希望这个解析能帮助你更好地理解这个函数的用途!


    在 Common Lisp 中,cond 是一个非常强大的条件控制结构,它允许根据多个不同的条件执行不同的代码块。你可以把 cond 想象成一个多路选择开关,根据不同的条件来选择运行哪个代码块。

    📋 cond 的基本结构

    cond 的基本语法如下:

    (cond
      (condition1
       (form1)
       (form2))
      (condition2
       (form3)
       (form4))
      (t
       (form5)))

    每个 condition 是一个条件表达式,后面跟着的是在该条件为真时要执行的形式(form)。当 cond 被执行时,它会依次检查每个条件,找到第一个为真的条件,然后执行对应的代码块。

    🔍 t 的含义

    cond 的上下文中,t 是一个特殊的常量,表示“真”。它相当于布尔值 true,并且在 Lisp 中总是返回真。因此,t 通常被用作 cond 的最后一个条件,充当“默认”或“兜底”的选项。

    例子解析

    让我们看看前面提到的 cond 示例:

    (cond
      ((equal (random 10) 1)
       (setf (text obj) "--(O. -(O)--")
       (print "RED LIGHT!")
       (rgb 255 0 0))
      (t
       (setf (text obj) "--(X. -(X)--")
       (print "I'm not looking..")
       (rgb 0 (random 255) (random 255))))

    在这个例子中,cond 检查第一个条件 ((equal (random 10) 1)。如果该条件为真,程序将执行相应的代码块,设置 obj 的文本为 "--(O. -(O)--",打印 "RED LIGHT!",并将颜色设置为红色(rgb 255 0 0)。如果这个条件不为真,则会跳过这个代码块,执行 t 后的代码块。

    具体执行流程

    1. 条件检查cond 会首先执行 (equal (random 10) 1),生成一个随机数并与 1 比较。如果相等,该条件为真。
    2. 执行代码块:如果条件为真,就执行对应的代码块,设置文本、打印消息并改变颜色。
    3. 默认情况:如果第一个条件不为真,程序将直接执行 t 后的代码块,设置文本为 "--(X. -(X)--",打印 "I'm not looking..",并随机生成颜色。

    🛠️ 使用 cond 的技巧

    • 多条件判断cond 是处理多条件判断的理想选择,特别是当条件数量较多时,cond 的结构比嵌套的 if 更加清晰。
    • 组合条件:你可以在每个条件中使用复杂的表达式,甚至组合多个比较。例如:
      (cond
        ((and (< x 10) (> x 0)) 'between-1-and-10)
        ((or (= x 0) (= x 10)) 'boundary)
        (t 'out-of-bounds))

    在这个例子中,cond 将根据 x 的值返回不同的符号,这使得代码更具可读性。

    总结

    cond 是 Common Lisp 中一个非常灵活和强大的条件选择工具,通过使用 t 作为默认条件,可以有效地处理所有可能的情况。理解 cond 及其用法,将帮助你在编写代码时更好地控制程序的流向,提升逻辑清晰度和可维护性。希望这个详细讲解能帮助你更好地掌握 cond 的用法!


    在 Common Lisp 中,比较操作是语言的重要组成部分,尤其是在处理数据结构和条件判断时。以下是对 equalequalpeqeql 的详细讲解,帮助你理解它们之间的区别和使用场景。

    📏 equalequalp

    1. equal

    • 功能equal 用于进行值的比较,支持复杂数据结构(如列表、数组等)的相等性检查。
    • 特性
    • 大小写敏感:在比较字符串时,equal 会区分大小写。例如,(equal "Hello" "hello") 返回 nil
    • 类型敏感equal 对于不同类型的数字(如整数 10 和浮点数 10.0)也会返回 nil。例如,(equal 10 10.0) 返回 nil

    示例

    (equal "Hello" "Hello") ; => T
    (equal '(1 2 3) '(1 2 3)) ; => T
    (equal '(1 2 3) '(1 2 3 4)) ; => NIL
    (equal 10 10.0) ; => NIL

    2. equalp

    • 功能equalp 也是用于比较值的函数,但它在比较时更加宽松。
    • 特性
    • 大小写不敏感:在比较字符串时,equalp 不会区分大小写。例如,(equalp "Hello" "hello") 返回 T
    • 类型不敏感equalp 对于数字的比较也更加灵活,例如,(equalp 10 10.0) 返回 T

    示例

    (equalp "Hello" "hello") ; => T
    (equalp '(1 2 3) '(1 2 3)) ; => T
    (equalp 10 10.0) ; => T

    📍 eqeql

    1. eq

    • 功能eq 用于比较两个对象是否是同一个内存地址,即是否是同一个对象的引用。
    • 特性
    • 主要用于符号和原子类型eq 主要用于比较符号、字符和小整数(通常在 -129 到 128 之间)。对于其他类型的对象,如列表或数组,eq 比较的是引用而不是内容。

    示例

    (eq 'a 'a) ; => T
    (eq 'a 'b) ; => NIL
    (eq 10 10) ; => T (小整数)
    (eq 256 256) ; => NIL (256 不在小整数范围之内)
    (eq '(1 2 3) '(1 2 3)) ; => NIL (不同的列表对象)

    2. eql

    • 功能eqleq 的扩展,除了支持对象引用的比较外,还可以比较数字和字符类型的值。
    • 特性
    • 用于数字和字符eql 可以用于比较相同的数字(包括整数和浮点数)和字符。
    • 对于符号和小整数:行为与 eq 相同。

    示例

    (eql 'a 'a) ; => T
    (eql 10 10) ; => T
    (eql 10 10.0) ; => NIL
    (eql 256 256) ; => T (256 是数字,比较值)
    (eql '(1 2 3) '(1 2 3)) ; => NIL (引用不同)

    总结

    • equal:用于值比较,支持复杂数据结构,大小写和类型敏感。
    • equalp宽松的值比较,大小写和类型不敏感,适合比较字符串和数字。
    • eq:检查对象引用是否相同,主要用于符号和小整数。
    • eql:扩展的 eq,支持数字和字符的值比较。

  • 🌊 在“包”与“系统”的海洋中航行:Common Lisp 的探险

    在这个信息爆炸的时代,编程语言如雨后春笋般层出不穷,但在这片繁茂的森林中,Common Lisp犹如一颗璀璨的明珠,闪耀着智慧的光芒。它的稳定性和灵活性,使得它成为了一种在真实世界中编写软件的强大工具。今天,我们将深入探讨Common Lisp中的“包”和“系统”,并通过一些趣味的比喻,让这个复杂的概念变得通俗易懂。

    📦 包的构造:符号的家园

    在Common Lisp中,包(Package)就像是一个个小社区,里面住着许多符号(Symbols)。这些符号就像社区中的居民,它们各自有着不同的角色和功能。想象一下,如果没有组织,居民们会变得混乱不堪,甚至可能会互相争吵。包正是在这样的背景下应运而生,它为符号提供了一个有序的居住环境。

    🏠 创建你的第一个包

    创建一个包的过程就像是为新社区立法。我们可以通过以下代码来创建一个简单的包:

    (defpackage :hello-package
      (:nicknames :hello-pkg)
      (:use :cl :clog)
      (:export :hello-world))

    在这个包中,我们定义了一个社区的名称(hello-package),设置了一个昵称(hello-pkg),并指定了它能使用的其他包(:cl 和 :clog)。最重要的是,我们通过:exporthello-world这个符号暴露给外界,就像是把社区的门打开,让外人也能进来。

    🧭 进入包的世界

    当你想要进入某个包时,可以使用IN-PACKAGE命令,就像是走进一个社区的大门。比如,我们可以通过以下命令进入hello-package

    (in-package :hello-package)

    此时,你就可以在这个包内创建和管理符号,而不必担心与其他包的符号发生冲突。

    🔗 ASDF与QuickLisp:构建系统的基石

    要想把你的包变成一个完整的系统,就需要用到ASDF和QuickLisp这两个强大的工具。它们就像是建筑工程师和材料供应商,帮助你将各种组件组合成一个完整的建筑。

    🏗️ 定义系统

    定义一个系统的过程就像是绘制一张建筑蓝图。以下是一个简单的系统定义示例:

    (asdf:defsystem #:hello-sys
      :description "Common Lisp - The Tutorial Part 3"
      :author "david@botton.com"
      :license "BSD"
      :version "0.0.0"
      :serial t
      :depends-on (#:clog)
      :components ((:file "hello")))

    在这段代码中,我们描述了系统的基本信息,包括名称、描述、作者以及依赖的包。最后,通过:components指定了系统中包含的文件。

    📦 快速加载系统

    一旦系统定义完成,我们可以使用QuickLisp来加载这个系统,这就像是按下了建筑的启动按钮:

    (ql:quickload :hello-sys)

    这时,所有相关的组件和依赖都会被自动加载,准备就绪。

    🎉 运行你的系统

    一旦系统被加载,我们就可以开始测试自己的代码了。调用hello-world函数,就像是打开了一扇通往新世界的大门:

    (hello-pkg:hello-world)

    如果一切顺利,你的浏览器将会打开,展示出“Hello World”的标语,仿佛在向你致以热烈的欢迎。

    🤔 总结与反思

    通过今天的探险,我们了解了Common Lisp中的包与系统的基本概念与使用方法。包为我们提供了组织符号的结构,而ASDF和QuickLisp则帮助我们将这些符号组合成一个完整的系统。正如《海底两万里》中所描绘的那样,编程的旅程充满了未知的挑战与惊喜,而Common Lisp便是我们探索这片海洋的强大潜艇。

    当然,编程之路并非一帆风顺,理解这些概念需要时间与实践。希望大家能在这个过程中,享受到编程带来的乐趣与成就感。

    📚 参考文献

    1. Common Lisp – The Tutorial Part 3, David Botton.
    2. The Evolution of Lisp, Paul Graham.
    3. Practical Common Lisp, Peter Seibel.
    4. ANSI Common Lisp, Graham, Paul.
    5. Lisp In Small Pieces, Christian Queinnec.

    在 Common Lisp 中,#:foo 表示一个未归属到任何包的符号,而不是全局符号。以下是对 #:foo 和全局变量的详细区别和解释:

    1. #:foo 的性质

    • 未归属符号#:foo 是一个独立的符号,它不是任何包的一部分。这意味着它不会与全局符号或其他包中的符号冲突。
    • 临时性:通常用于临时用途或内部数据结构,适合在函数内部或局部上下文中使用。

    2. 全局变量的性质

    • 全局变量:全局变量是在程序的全局上下文中定义的符号,通常是在特定包中定义且可以被其他包访问。
    • 可见性:全局变量可以在多个包中被引用,只要适当指定包名(例如 package-name:variable-name),并且它们的值在整个程序的生命周期中保持有效。

    3. 关键区别

    • 命名空间
      • #:foo 是未归属的,意味着它既不在全局符号表中,也不在任何包中。
      • 全局变量是包的一部分,通常具有明确的包名和作用域。
    • 冲突
      • #:foo 不会与其他符号发生冲突,因为它是独立的。
      • 全局变量可能与其他包中的同名变量发生冲突,除非在使用时明确指定包。
    • 使用场景
      • #:foo 适合临时用途,如在宏或函数内部使用。
      • 全局变量用于需要在多个模块或包之间共享的状态或数据。

    示例

    ;; 定义全局变量
    (defparameter *my-global* 42)
    
    ;; 使用 #:foo
    (let ((#:foo 100))  ; #:foo 是一个临时符号
      (print #:foo))    ; 输出 #:foo,没有冲突
    
    ;; 使用全局变量
    (print *my-global*)  ; 输出 42

    总结

    • #:foo 是一个未归属的符号,通常用于临时用途,不会与其他符号发生冲突。
    • 全局变量是在程序的上下文中定义的符号,可以被多个包访问,可能会与其他包中的同名符号冲突。
    • 选择使用 #:foo 还是全局变量,取决于你的具体需求和上下文。

    在 Common Lisp 中,#:#' 是两种不同的读宏(reader macro),它们用于不同的目的。以下是它们的比较:

    1. #' 的含义

    • 功能#' 用于获取一个符号所代表的函数的引用。它是 function 的缩写,常用于指定函数对象。
    • 用法
    (defun my-function ()
      (print "Hello, World!"))
    
    (let ((f #'my-function))  ; 获取 my-function 的引用
      (funcall f))            ; 调用 my-function
    • 用途:主要用于高阶函数、回调和动态调用函数。

    2. #: 的含义

    • 功能#: 用于创建一个未归属的符号,通常用于标识符的命名,特别是在避免符号冲突时。它创建的符号不会与当前包或全局符号表中的符号发生冲突。
    • 用法
    (let ((s #:foo))  ; 创建一个未归属的符号
      (print s))      ; 输出 #:foo
    • 用途:适用于临时用途、宏展开或内部数据结构,常用于需要确保符号唯一性的场景。

    3. 关键区别

    特性#'#:
    目的获取函数的引用创建未归属的符号
    使用场景高阶函数、回调避免符号冲突、临时用途
    符号类型返回一个函数对象返回一个未归属的符号
    冲突可能与全局或其他包中的符号冲突不会与任何其他符号冲突

    总结

    • #' 用于获取函数的引用,适合高阶函数和动态调用。
    • #: 用于创建未归属的符号,适合临时用途和避免符号冲突。

    领略共生:Lisp 中的函数之美 🌱

    在编程的世界里,有一种语言如同一位优雅的舞者,轻盈而灵动,仿佛在与代码共舞——这就是 Lisp。正如电影《公爵与我》中所说:“你总是使用那个词。我不认为它意味着你认为的那样。”今天,我们就来聊聊 Lisp 中的函数,揭开其神秘的面纱。

    🚀 函数的速度与灵活性

    在编程的宇宙中,Lisp 以其不可思议的速度和灵活性著称。想象一下,你在 REPL(Read-Eval-Print Loop)中如同一位魔法师,随意施展咒语,看到代码瞬间生效。为了让这些魔法长久地存在,我们需要将它们植根于系统中,这就需要定义命名函数。函数不仅是执行操作的工具,更是数据的一部分,可以作为参数传递或从另一个函数返回。Lisp,作为函数式编程的祖师爷,赋予了函数以新的生命。

    🏗️ 命名函数的构建

    让我们来构建一个简单的系统,名为 hello-sys。首先,我们需要加载这个系统:

    (ql:quickload :hello-sys)

    在我们的 hello-package 中,定义了两个命名函数,一个是导出的 hello-world,另一个是私有的 hello-private

    (defpackage :hello-package
      (:nicknames :hello-pkg)
      (:use :cl :clog)
      (:export :hello-world))
    
    (in-package :hello-package)
    
    (defun hello-private (body)
      "Create a div on new pages containing - hello world"
      (create-div body :content "hello world"))
    
    (defun hello-world ()
      "Initialize CLOG and open a browser"
      (initialize 'hello-private)
      (open-browser))

    在这里,defun 是定义命名函数的魔法咒语。每个函数都可以有参数,甚至可以没有参数。想象一下,调用 hello-world 就像是打开一扇窗,让阳光洒进来,而 my-func 则是一个简单的加法函数:

    (defun my-func (parameter1 parameter2)
      "A DOC string used for documenting a named function"
      (+ parameter1 parameter2))

    ⚙️ 可选与命名参数

    函数的灵活性不仅体现在可以无参数或多参数上,还可以使用可选参数和命名参数。下面的例子展示了如何使用可选参数:

    (defun my-func2 (parameter1 &optional parameter2)
      "return the sum of parameters unless only parameter1 provided."
      (if parameter2
          (+ parameter1 parameter2)
          parameter1))

    调用 my-func2 的时候,你可以选择只传入一个参数,或者两个参数。就像在餐厅里,你可以选择点一份主菜,也可以加一个配菜。

    🔍 匿名函数的魅力

    不仅有命名函数,Lisp 还拥有一种神秘的存在——匿名函数。通过 lambda 表达式,我们可以创建没有名字的函数,就像在舞台上闪烁的流星,转瞬即逝,但却璀璨夺目:

    (lambda (lambda-list) body)

    hello-private 函数中,我们使用了匿名函数来处理颜色的变化:

    (defun hello-private (body)
      "Create a div on new pages containing - hello world
      That when clicked changes color."
      (set-on-click (create-div body :content "hello world")
                    (lambda (obj)
                      (setf (color obj) (rgb (random 255)
                                             (random 255)
                                             (random 255))))))

    这里,set-on-click 生成的 lambda 表达式就像是一位调皮的色彩师,随意调配出五彩斑斓的颜色。

    🎉 结论:函数的魔力

    函数在 Lisp 中不仅仅是操作数据的工具,它们可以有副作用,比如打印输出或生成图形,甚至可以作为数据本身。正是这些特性,让 Lisp 成为一种独特的函数式编程语言。

    📚 总结

    从上述内容中,我们可以提炼出以下几点:

    1. Lisp 使用“LISt Processing”。
    2. 列表的第一个元素是操作符,其余元素是参数。
    3. Lisp 拥有不同的开发周期和模型,程序是逐步生长的。
    4. 在 Lisp 程序中,包是结构的顶层,组织符号而非文件。
    5. 完全合格的符号以 package-name:symbol-name 编码。
    6. 符号可以命名函数、变量等。
    7. 符号也可以作为数据类型使用,前面加上撇号。
    8. 我们通过 IN-PACKAGE 在包之间导航。
    9. 使用 DEFPACKAGE 定义包。
    10. 系统通过一个目录定义,该目录与 .asd 配置文件同名。

    Lisp 的函数就如同万花筒中的光影,变化多端,令人目不暇接。它们不仅让我们能够高效地处理数据,更让编程的过程充满乐趣与创造力。


    在 Common Lisp 中,&optional&key 是用来定义函数参数的两种不同方式,它们各有千秋,适合不同的使用场景。让我们来逐一探讨这两者的区别,像是在一场精彩的辩论中,看看谁更胜一筹。

    🎭 可选参数:&optional

    &optional 用于定义可选参数,这意味着你可以选择性地提供这些参数。如果你不提供可选参数,Lisp 会将它们的值设置为 nil。让我们通过一个示例来看看:

    (defun my-func2 (parameter1 &optional parameter2)
      "Return the sum of parameters unless only parameter1 is provided."
      (if parameter2
          (+ parameter1 parameter2)
          parameter1))

    在这个例子中,parameter1 是必填参数,而 parameter2 是可选的。如果调用时只提供 parameter1,那么 parameter2 就会是 nil,此时函数返回 parameter1 的值。调用方式如下:

    (my-func2 5)      ; 返回 5
    (my-func2 5 3)    ; 返回 8

    🌈 命名参数:&key

    &optional 不同,&key 用于定义命名参数。命名参数允许你在调用函数时通过参数名来指定参数,而不是仅仅依靠它们的位置。这使得函数的调用更加灵活和可读。来看一个例子:

    (defun my-func3 (parameter1 &key parameter2)
      "Return the sum of parameters unless only parameter1 is provided."
      (if parameter2
          (+ parameter1 parameter2)
          parameter1))

    在这种情况下,你可以按如下方式调用函数:

    (my-func3 5 :parameter2 3)  ; 返回 8
    (my-func3 5)                 ; 返回 5

    通过这种方式,你可以更清楚地表明你所提供的参数是什么,尤其是当函数有多个可选参数时,这种方式让代码更加易读。

    🏆 总结:选择你的战斗方式

    • &optional:用于定义可选参数,适合简单的函数调用,特别是参数较少时。
    • &key:用于定义命名参数,适合参数较多、需要提高可读性和灵活性的情况。

    选择哪种方式,完全取决于你的具体需求。如果你想让函数调用更清晰,尤其是当函数参数众多时,使用 &key 是个不错的选择;而如果只是简单的可选参数,&optional 则更为直观。

    这就像在一个派对上,你可以选择直接邀请所有人(&optional),或者通过特定的邀请函来让每个人知道他们的角色(&key)。选择权在你!🎉


    在 Common Lisp 中,冒号 : 有着特殊的含义,尤其是在使用命名参数时。让我们一起深入了解一下这个小符号的魔力吧!🔍

    🌟 冒号的含义

    在函数调用中,冒号用于表示命名参数(&key 参数)。当你在调用一个使用了 &key 的函数时,冒号后面跟的是参数的名称,这样你可以明确指定要传递的参数。例如:

    (my-func3 5 :parameter2 3)

    在这个例子中:

    • 5 是位置参数 parameter1 的值。
    • :parameter2 是命名参数,后面的 3 是传递给 parameter2 的值。

    命名参数的优势

    使用冒号来指明参数名有几个优点:

    1. 可读性:通过明确指定参数名,代码的意图变得更加清晰。例如,(my-func3 5 :parameter2 3)(my-func3 5 3) 更容易理解,因为你可以一眼看出 3 是赋给 parameter2 的值。
    2. 灵活性:当函数有多个可选参数时,使用命名参数可以让你在调用时灵活选择,只传递你关心的参数。例如,如果你只想传递 parameter2,可以这样调用:
       (my-func3 5 :parameter2 3)  ; 传递 parameter1 和 parameter2
       (my-func3 5)                 ; 只传递 parameter1,parameter2 为 nil
    1. 顺序无关:使用命名参数时,参数的顺序不再重要。你可以随意组合参数,只需确保使用了正确的参数名。

    🎉 小结

    在 Common Lisp 中,冒号 : 用于指示命名参数,这使得函数调用更加灵活、可读和清晰。它让代码更具表达力,就像为你的函数调用加上了一层精美的装饰,让它不再单调乏味!

    所以,下次在使用命名参数时,记得给你的代码加点颜色,充分利用这个小小的冒号吧!✨


    在 Common Lisp 中,你可以使用 Unicode 字符来定义 lambda 表达式,甚至可以用 Unicode 字符作为函数名。这就像为你的代码穿上了华丽的衣服,让它更具特色和趣味性。实际上,Lisp 是相当灵活的,允许许多非传统的字符来丰富你的代码风格。

    🌟 使用 Unicode 字符定义 Lambda

    首先,让我们看看如何使用 Unicode 字符来定义一个简单的 lambda 函数。假设我们想用希腊字母 λ(Unicode: U+03BB)来表示我们的 lambda 函数:

    (defun λ (x)
      (* x x))

    在这个例子中,我们定义了一个名为 λ 的函数,它接受一个参数 x,并返回 x 的平方。现在,我们可以像调用任何其他函数一样调用这个函数:

    (λ 3)  ; 返回 9

    🌈 多样化字符的乐趣

    Lisp 的这种灵活性不仅限于 lambda,你可以使用其他 Unicode 字符来定义变量、参数和函数名。这就像是在编程中加入了一点艺术的色彩,让代码不仅仅是逻辑的堆砌,更是一种表达。

    例如,你可以使用中文字符来定义函数:

    (defun 加法 (a b)
      (+ a b))

    调用这个函数时,你可以写:

    (加法 2 3)  ; 返回 5

    ⚠️ 注意事项

    尽管使用 Unicode 字符可以让你的代码更具个性,但也要注意以下几点:

    1. 可读性:虽然对你来说可能很有趣,但其他人可能不习惯看到 Unicode 字符,可能会影响代码的可读性。
    2. 兼容性:确保你的开发环境和版本控制系统支持 Unicode 字符。如果不支持,可能会导致意外的错误。
    3. 调试:在调试时,使用非标准字符可能会让错误信息变得更加复杂,尤其是在大型项目中。

    🎉 结论

    总之,使用 Unicode 字符来定义 lambda 和其他函数在 Common Lisp 中是完全可行的。这种灵活性不仅让编程变得更有趣,还为你提供了更多的表达方式。只要确保在可读性和兼容性之间找到平衡,你就可以大胆地让你的代码焕发出独特的光彩!✨


    在 Common Lisp 中,确实可以使用 Unicode 字符 λ(U+03BB)来替换 lambda,但这并不是一个直接的替换。让我们深入探讨这一点。

    🌟 使用 λ 的潜力

    在 Lisp 中,lambda 是一个特殊形式,用于定义匿名函数。虽然你可以用 λ 来作为函数名,但如果你想直接使用 λ 来定义一个匿名函数,实际上是不可行的。Lisp 仍然要求使用 lambda 关键字来明确表示你正在定义一个匿名函数。

    例如,以下是定义一个匿名函数的传统方式:

    (lambda (x) (* x x))

    如果你想用 λ 来表示这个函数,可以将其用作函数名,但仍需使用 lambda 关键字:

    (defun λ (x) (* x x))

    因此,调用这个函数时,你可以这样做:

    (λ 3)  ; 返回 9

    🔍 λ 作为函数名与 lambda 的区别

    在 Lisp 中,lambda 关键字是语言的核心部分,用于创建匿名函数。而 λ 作为一个普通的符号,只能用于命名函数。换句话说,虽然你可以用 λ 来命名函数,但不能用它来替代 lambda 关键字本身。

    🎉 小结

    • 直接替换:不能用 λ 替代 lambda 关键字来定义匿名函数。
    • 作为函数名:你可以使用 λ 作为函数名,定义并调用它,就像使用任何其他标准字符一样。

    所以,尽管 λ 不能直接替代 lambda,你仍然可以利用它为你的代码增添一抹独特的风采。编程就是这样一门艺术,尝试不同的符号和表达方式,可能会让你的代码更加生动有趣!🎨✨


  • 🚀 探索 Common Lisp 的奇妙世界

    在编程的宇宙中,有一种语言如同一位伟大的魔法师,掌控着各种神秘的力量,那便是 Common Lisp!今天,我们将踏上这段奇妙的旅程,深入探讨这门语言的核心概念,尤其是它的符号和开发流程。准备好迎接挑战了吗?别担心,我们会像打拳击一样,耐心而又坚定地前行。

    🌱 初识 Common Lisp

    Common Lisp 并不是你所熟悉的编程语言,像是用木刀打靶的初学者,它有自己独特的开发循环和思维方式。你可能习惯于编辑 -> 编译 -> 执行的传统开发流程,但在 Lisp 中,事情大为不同。

    想象一下,Emacs 是你灵活的拳击手套,而 SLIME 就是那根连结你和 Lisp 图像的“脐带”。通过它,你可以实时与 Lisp 对话,像是和一位训练有素的教练进行心灵的交流。你可以在 REPL(读取—求值—打印循环)中输入命令,立刻得到反馈,仿佛在进行一场无声的对话。

    CL-USER> (print "hello")
    "hello"

    就这样,你的第一个命令便在 REPL 中成功执行,返回了 “hello”。这不是简单的输出,而是你与 Lisp 之间一场精彩的互动,仿佛在为你的编程生涯揭开序幕。

    📂 文件与包的奇妙关系

    在 Common Lisp 中,文件并不如其他语言那样提供程序的结构。相反,文件中的每一行代码都像是你在 REPL 中输入的内容,直接注入到 Lisp 图像中。想象一下,你的一切代码都如同一锅浓汤,随时可以舀出,随时可以添加新料。

    那么,如何管理这些代码呢?这就要提到“包”的概念了。包就像是你在厨房中为不同食材准备的碗,每个碗里都是独特的配方。Common Lisp 中的符号就通过包进行组织。例如,common-lisp:print 这个符号指向了打印函数,而在默认的 CL-USER 包中,你可以直接使用 print 来调用它。

    (load "~/common-lisp/hello.lisp")

    当你加载一个文件时,你不仅仅是将代码加入到 Lisp 图像中,而是将其融入到整个程序的结构中。每个包都是一片田野,而符号则是那一颗颗播种的种子,未来会发芽成长,结出丰硕的果实。

    🧩 符号的魔力

    现在,让我们来聊聊符号,这个在 Common Lisp 中具有重要地位的概念。符号不仅仅是代码中的名字,它们是与程序中各个元素的连接点,几乎可以说是语言的灵魂。

    你可以在 REPL 中直接创建符号,或者在文件中定义它们。使用 defun 定义函数,使用 defvar 定义变量,这些都是符号的运用。看一下这个例子:

    (defun main ()
      (print "hello"))
    
    (defvar *cool* 123)
    (setf *cool* nil)

    在这个代码片段中,main*cool* 都是符号。main 是一个函数名,而 *cool* 则是一个全局动态变量。更有趣的是,Lisp 允许你将函数本身赋值给变量,这让代码变得灵活而富有表现力。

    🎨 符号的使用与数据类型

    符号在 Lisp 中不仅仅是用于命名的工具,它们本身也可以作为数据类型使用。通过在符号前加上一个撇号(’),你可以将它们作为类型处理。例如:

    (setf *cool* 'a-symbol)

    这里的 a-symbol 不再是一个字符串,而是一个符号。想象一下,你可以将它用作图形绘制的参数,传递不同的符号来表现不同的情感——当你传递 'love 时,它画出花朵,而传递 'hate 时,它则画出骷髅。这种灵活性为编程增添了不少乐趣。

    🏁 总结与展望

    今天我们探索了 Common Lisp 的几个重要概念:REPL 的使用、文件与包的关系、符号的定义与应用。虽然我们走得慢而稳,但这正是通向编程深处的必要之路。理解这些基础概念,将为你后续的编程旅程打下坚实的基础。

    在接下来的部分中,我们将加速前进,深入探讨 Lisp 的更多奇妙之处。准备好迎接更复杂的挑战了吗?让我们一起继续探索这个充满可能性的编程世界吧!

    📚 参考文献

    1. Common Lisp: The Language, 2nd Edition
    2. Common Lisp – The Tutorial Part 1
    3. https://github.com/rabbibotton/clog/blob/main/LEARN.md
    4. https://lisp-lang.org/learn/getting-started/
    5. https://gigamonkeys.com/book/

    保持好奇,勇敢探索!


    functionsymbol-function 比较

    functionsymbol-function 都可以用来获取函数的引用,但它们之间有一些关键的区别和适用场景。以下是它们的比较:

    1. 语法和用法

    • function:
      • 用法:(function symbol)
      • 适用于已定义的函数。
      • 直接获取函数的引用。
      (defun my-function ()
        (print "Hello!"))
    
      (setf my-var (function my-function))
    • symbol-function:
      • 用法:(symbol-function 'symbol)
      • 适用于任何符号,无论它是否已经被定义为函数。
      • 可以检查符号是否有对应的函数,并获取其引用。
      (setf my-var (symbol-function 'my-function))

    2. 错误处理

    • function:
      • 如果符号未定义为函数,将导致错误。
    • symbol-function:
      • 如果符号未定义为函数,返回 nil,不会产生错误。

    3. 适用场景

    • function:
      • 更常用于直接调用和传递已知函数的引用,语法更简洁。
    • symbol-function:
      • 更适用于动态场景,比如需要检查符号是否已定义为函数,或在需要处理未定义函数的情况下使用。

    4. 赋值

    • symbol-function 允许你重新定义一个函数的实现:
      (setf (symbol-function 'my-function) (lambda () (print "New implementation!")))
    • function 不能直接用于赋值。

    总结

    • 使用 function 时,语法简洁,适合已知函数的引用。
    • 使用 symbol-function 时,提供了更大的灵活性,允许对未定义符号的检查和赋值。

    在 Lisp 中,#' 是用来获取符号所代表的函数的标准方式。如果你不想使用 #',还有其他方法,但这取决于你想要实现的具体功能。

    1. 使用 function

    你可以用 function 关键字替代 #',效果相同:

    (setf *cool* (function main))

    2. 使用 symbol-function

    如果你希望直接从符号中获取函数对象,可以使用 symbol-function

    (setf *cool* (symbol-function 'main))

    3. 不使用函数名

    如果你有一个匿名函数(lambda 表达式),则可以直接将它赋值给变量:

    (setf *cool* (lambda () (print "Hello, World!")))

    总结

    虽然有其他方法可以引用函数,但 #' 是最常用和推荐的方式,因为它简洁且易于理解。使用 functionsymbol-function 也是有效的,但可能在可读性上稍逊一筹。


    在 Common Lisp 中,let* 的语法要求使用括号来定义变量和它们的初始值。以下是对这个语法结构的详细解释:

    1. let* 的基本结构

    let* 的基本结构是:

    (let* ((var1 value1)
           (var2 value2)
           ...)
      body)
    • 外层括号:表示 let* 表达式的整体。
    • 内层括号:每对内层括号表示一个变量及其对应的初始值。

    2. 变量的定义

    let* 中,每个变量都被放在一个括号对中,以便于定义和初始化:

    (let* ((s "This is a string
    that spans multiple lines."))
      ...

    这里的 (s "This is a string...") 是一个内层括号,表示变量 s 的定义,"This is a string..." 是它的初始值。

    3. 支持依赖关系

    使用 let* 而不是 let 的原因之一是,let* 支持变量之间的依赖关系。在 let* 中,后面定义的变量可以使用前面变量的值。例如:

    (let* ((x 10)
           (y (+ x 5)))  ; y 可以使用 x 的值
      (print y))      ; 输出 15

    在这个例子中,y 的值依赖于 x 的值。

    结论

    因此,使用两层括号是为了在 let* 中清晰地定义每个变量及其初始值。这是 Common Lisp 语法的一部分,使得代码结构更加明确。


  • 🚀 自然语言与编程的奇妙旅程:探索 Common Lisp 的美与力量

    引言 🌌

    在这个信息爆炸的时代,编程语言如同万花筒般层出不穷,每一种语言都有它独特的魅力。然而,今天我们要聚焦的却是一种被许多人视为“丑陋”的语言——Common Lisp。可别被这个外表所迷惑,正如保罗·格雷厄姆所言:“如果 CL 很丑,为什么我还要用它和写关于它的文章呢?因为 Lisp 实在是太强大了,即便是一个丑陋的 Lisp,也比其他语言更可取。”从某种意义上来说,Lisp 就像是编程语言中的“丑小鸭”,在它内敛的外表下,蕴藏着无穷的力量。

    📜 旅程的开始

    今天,我们将一起踏上探索 Common Lisp 的旅程。我,一个写作水平堪忧的程序员,以及你,一个渴望学习 Common Lisp、CLOG 或者神秘的卡巴拉软件设计的好奇者。我们的目标是迅速让你掌握足够的 Common Lisp,以便你能用这种充满括号的丑陋美丽语言,编写出比任何其他语言更强大、更迅速的软件。

    规则 1:忽略括号,关注缩进 🔍

    在 Lisp 的世界里,S 表达式(S-expressions)是其核心。一个括号后跟一个操作符和参数,最后以另一个括号结束。例如,(+ 1 2) 的结果是 3。这种表达方式使得 Lisp 的结构清晰,但也意味着会有大量的括号出现。然而,别担心,人的大脑有着奇妙的解析能力,只需关注缩进,括号就不再是问题。例如:

    (tagbody
      (print "Hello")
      (go 10))

    一旦你开始关注缩进,Lisp 将会变得和其他语言一样易于理解。

    📈 Lisp 的力量:同构性与多范式

    Lisp 语言的简单语法和括号的使用,使其展现出强大的同构性(homoiconicity)。这意味着代码可以以数据的形式进行处理,构建出更灵活的程序。Lisp 不仅是一种函数式语言,还支持面向对象、过程式等多种编程范式。简而言之,你可以在 Lisp 中随心所欲地编写代码,只要它清晰易读,符合你的需求。

    规则 2:清空思维,使用力量 🧘

    Lisp 的多范式特性使得它可以适应各种编程风格。无论是函数式编程、面向对象编程,还是过程式编程,Lisp 都能游刃有余。重要的是,编写代码的时候要保持清晰和简洁,这样才能在复杂的项目中游刃有余。

    规则 3:稳定性的重要性 🔒

    Lisp 自 1958 年被指定以来,其稳定性一直是其成功的关键因素之一。Common Lisp 于 1994 年成为 ANSI 标准,并自此保持稳定。这种稳定性使得开发者能够在坚实的基础上构建应用,正如一座雄伟的建筑,必须有一个坚固的地基。

    规则 4:社区的力量 💬

    加入 Lisp 社区,你会发现一群乐于分享经验和知识的人。他们的交流虽然直接,但却是确保大家在同一条船上的有效沟通方式。在这里,你可以得到无尽的支持和灵感。

    规则 5:工具的重要性 🛠️

    编程语言的成功往往取决于其工具的丰富性与开源性。Common Lisp 拥有众多开源编译器和强大的工具链,这为开发者提供了极大的便利。记住,工具是你最好的朋友,善待它们,你的编程之旅将更加顺畅。

    🌈 我们的旅程将带你去哪里

    作为一名“战壕程序员”,我不是为了为你编写标准或手册,但我希望能激发你用 CLOG 编写 Common Lisp 代码的热情。接下来,我们将一起探索:

    • 编写商业/IT 应用程序所需的最小操作符集。
    • 学习基本的 Lisp 习惯用法。
    • 引导你深入探索 Lisp 和软件开发的深度。
    • 从一开始就了解并使用并行计算——是的,这在今天是必须的!
    • 当然,成为一个 CLOGer!

    结论 🌟

    在这条编程的旅程中,你不仅会学习到 Common Lisp 的基本知识,还会领悟到编程的真正艺术。Lisp 不仅仅是一种语言,它是一种思维方式,一种创造的力量。让我们一起在这个奇妙的世界中,探索无尽的可能性,编写出更美好的代码,创造出更精彩的未来。

    参考文献 📚

    1. Graham, P. (2002). Common Lisp: The Power of the Parentheses.
    2. Ichbiah, J. (1980). Ada Programming Language.
    3. McCarthy, J. (1958). Lisp Programming Language.
    4. CLOG Documentation. Retrieved from https://github.com/rabbibotton/clog.
    5. The Lisp Community. Retrieved from https://www.cliki.net/.

    希望这段旅程能激励你深入探索 Common Lisp 的广阔天地,并在其中找到属于自己的编程乐趣!

  • 在Windows 64位系统上从零开始安装Common Lisp的指南

    在这个快速变化的科技世界中,编程语言的选择就像选择早餐一样重要,但却更加复杂。今天,我们要一起踏上安装Common Lisp的旅程,特别是在Windows 64位系统上。无论你是编程新手,还是经验丰富的老手,这个指南都将为你提供清晰的步骤,确保你能够顺利地开始你的Lisp之旅。

    🏗️ 准备工作:下载与安装

    首先,你需要为你的编程环境搭建一个坚实的基础。以下是安装Common Lisp所需的几个主要组件。

    1. 下载并安装rho-emacs

    rho-emacs是一个轻量级的Emacs版本,非常适合Lisp开发。你可以从这里下载它。在安装时,选择你的“主文件夹”为C. \Users\你的用户名。这一步就像为你的编程之旅选一个舒适的座椅。

    2. 安装SBCL

    SBCL(Steel Bank Common Lisp)是一个广泛使用的Common Lisp实现。你可以从SBCL官网下载适合Windows的版本。安装SBCL就像给你的电脑装上了一个强大的大脑。

    3. 获取64位GIT

    GIT不仅是一个版本控制工具,它还会为你安装一些必需的SSL文件和基本的Unix工具,比如bash。访问GIT官网下载并安装。安装时,请选择行结尾设置为“checkout as-is, commit unix-style line endings”,这样SBCL在处理文件时会更顺畅。

    4. 获取SQLite DLL

    为了让你的Lisp程序能够与数据库无缝连接,你需要下载64位SQLite DLL。访问SQLite官网,下载后将内容复制到C. \Program Files\Git\mingw64\bin目录。

    5. 下载QuickLisp

    打开Git Bash(从Windows应用中找到),然后运行以下命令来下载QuickLisp:

    cd
    curl -o /tmp/ql.lisp http://beta.quicklisp.org/quicklisp.lisp

    QuickLisp就像Lisp的超市,让你轻松获取各种库和工具。

    🚀 快速启动:安装与配置

    现在,我们已经准备好了所有必要的工具,接下来就是安装与配置QuickLisp的过程了。

    6. 安装QuickLisp

    在Git Bash中继续运行以下命令:

    sbcl --no-sysinit --no-userinit --load /tmp/ql.lisp \
        --eval '(quicklisp-quickstart:install :path "~/.quicklisp")' \
        --eval '(ql:add-to-init-file)' \
        --quit
    sbcl --eval '(ql:quickload :quicklisp-slime-helper)' --quit

    这一步骤像是给你的Lisp环境注入了强心针,让它变得更加灵活和强大。

    7. 配置Emacs

    运行rho-emacs(建议将其添加到PATH或创建一个脚本),输入以下命令:

    /c/Program\ Files/rho-emacs/rho

    使用C-x-f创建文件~/.emacs.d/init.el,并添加以下内容:

    (load (expand-file-name "~/.quicklisp/slime-helper.el"))
    (setq inferior-lisp-program "sbcl")

    这是让Emacs与SBCL连接的桥梁。

    8. 安装UltraLisp

    UltraLisp是一个集合了许多现代Lisp软件的发行版,安装它可以让你更轻松地访问各种工具和库。运行以下命令:

    sbcl --eval '(ql-dist:install-dist "http://dist.ultralisp.org/" :prompt nil)' --eval '(ql:update-all-dists)' --quit

    此时,你的Lisp环境正在逐步壮大,像一棵茁壮成长的树。

    🧩 完成安装:CLOG与Emacs的结合

    9. 安装CLOG

    CLOG是一个功能丰富的Lisp IDE,可以与Emacs无缝集成。运行如下命令来安装它并启动CLOG构建器:

    sbcl --eval '(ql:quickload :clog/tools)' --eval '(clog-tools:clog-builder)'

    这一步将你的编程环境提升到了一个新的高度,使得编写Lisp代码变得轻松愉快。

    10. 重启Emacs

    在完成所有这些步骤后,别忘了重启Emacs。可以使用C-x C-y退出Emacs,然后重新启动:

    /c/Program\ Files/rho-emacs/rho

    接下来,运行Slime,使用M-x slime命令。

    📝 注意事项

    在Windows上安装时,可能会遇到一些DLL文件的问题。别担心,你可以从这里下载所需的DLL:clogframe.zip,并使用clograme.exe与CLOG配合使用。

    结尾

    恭喜你!现在你已经在Windows 64位系统上成功安装了Common Lisp和相关工具。希望这个指南能帮助你更好地理解Lisp编程的乐趣。就像一场美妙的音乐会,编程的每一个音符都在等待你去谱写。

    参考文献

    1. rho-emacs
    2. SBCL
    3. GIT
    4. SQLite
    5. QuickLisp

    如缺少openssl,则需要安装:Win32/Win64 OpenSSL Installer for Windows – Shining Light Productions (slproweb.com)

    https://slproweb.com/download/Win64OpenSSL_Light-3_3_2.msi

    并合理配置路径,使得sbcl能找到对应的DLL。


  • CLOG:构建现代用户界面的全新框架

    🌐 CLOG的概念目的

    在这个信息爆炸的时代,构建用户友好的界面就像在一片沙漠中找到水源一样重要。CLOG——一个为图形用户界面提供的框架,旨在利用最新的网络技术,帮助开发者创造出与众不同的应用程序。无论是小型单用户工具还是庞大的多用户并发应用,CLOG都能提供一个跨平台的解决方案,让每一个应用的界面都闪闪发光。

    🚀 底层技术

    运输机制

    CLOG的通信机制可谓是现代技术的结晶。它采用了客户端/服务器架构,初始引导使用HTTP协议,而后续通信则依赖于WebSocket。简单来说,WebSocket就像是CLOG的“高速公路”,使得数据在服务器和客户端之间快速流动。

    在CLOG应用的启动阶段,一个HTML引导页面和JavaScript脚本会帮助建立WebSocket连接。一旦连接建立,所有的消息交换都在JavaScript的控制下进行,这一切对于用户来说是透明的,仿佛魔术般的无缝体验。

    用户界面的初始状态建立

    在CLOG中,建立用户界面的初始状态就像调制一杯完美的鸡尾酒,有三种主要的方式:

    1. 使用初始引导文件:就像传统的网络服务器一样,CLOG允许通过HTTP服务初始UI。开发者可以将任何包含boot.js脚本的HTML文件作为引导文件,轻松实现服务器端的控制。
    2. 使用CLOG API从零开始构建UI:对于那些喜欢DIY的开发者,CLOG提供了一套API,允许他们从头开始打造完美的界面,简直是程序员的天堂。
    3. 混合方法:CLOG Builder面板可以创建复合组件,通过批量写入HTML/JS到浏览器,实现更高效的UI构建。

    性能考量

    在性能方面,方法1和3相较于Ajax性能优越,而方法2则稍显逊色,但对于完全无HTML和无JS代码的应用场景,依然具有其独特的优势。

    ✨ 初始化后的操作

    CLOG不仅仅是一个框架,它还是一个事件驱动的系统。从CLOG 1.2版本开始,新增的“呈现”系统允许Lisp对象和CLOG对象之间的双向绑定,使得数据模型和业务逻辑的使用变得更加自然,几乎不需要依赖CLOG API。

    CLOG应用可以完全无状态运行,利用post/get与任何其他Web框架一样,并且能够像传统应用一样进行状态管理。而更重要的是,CLOG赋予了开发者更高的响应性,使得应用能够随时随地与用户互动。

    可靠性

    在可靠性方面,CLOG比传统的客户端/服务器配置更具优势。它的设计旨在应对不理想的环境,确保即便在中断的情况下也能快速恢复。虽然完全的断开连接需要在设计中加以考虑,但CLOG依然能在同一台机器上实现连接的自动恢复。

    📈 可扩展性

    CLOG的可扩展性广泛,既可以用于提供静态页面,也可以构建服务器端生成的页面,甚至是客户端页面的服务器控制构建设计。无论选择哪种方式,CLOG的可扩展性都与其他基于Web的系统相当。

    然而,CLOG独特之处在于它能够开发出支持并发用户交互的系统,适用于大型商业应用或任何需要实时信息传递的应用。这样的能力使得CLOG能够更接近传统的客户端/服务器应用的扩展性。

    🏁 结论

    感谢您深入了解CLOG的工作原理。希望本文能为您提供清晰的见解,帮助您理解CLOG目前的能力以及它的不足之处。如果您有任何需求,请随时联系我,让CLOG能够满足更广泛的应用场景,从单用户工具到大规模的并发多用户系统,助力于每一个开发者的梦想成真。


    参考文献

    1. CLOG Technical Overview and Purpose. GitHub Repository
    2. WebSocket Protocol. RFC 6455
    3. JavaScript: Understanding the Weird Parts. Udemy Course
    4. Event-Driven Programming in JavaScript. MDN Web Docs
    5. Scalable Web Applications: The Future of Software Development. Article
  • 云原生时代下的轻量级利器:Nomad 集群管理与微服务部署调度

    👋 在云原生、容器化、微服务、服务网格等概念风靡的当下,Kubernetes 已经成为了云原生计算平台的事实标准。但 Kubernetes 的学习曲线较高,而且随着其功能不断扩展,维护成本也随之增加。那么,是否所有场景都需要部署一个庞大的 Kubernetes 集群呢?是否有更轻量级的方案能够满足我们的需求?

    💡 答案是肯定的!Hashicorp 公司推出的 Nomad 工具就提供了轻量级的集群管理和微服务部署调度方案,它足够灵活,能够满足各种场景下的需求。

    🚀 本文将带你深入探索 Nomad 的世界,并通过实际案例演示如何使用 Nomad 实现集群管理和微服务部署调度。

    一. Nomad 集群的搭建:轻装上阵,快速起航

    🏗️ Nomad 是一个基于 Go 语言实现的集群管理和工作负载调度器,它支持 Docker 容器、虚拟机、原生可执行程序等多种驱动形式的工作负载调度,并支持跨数据中心调度。

    🤝 Nomad 与 Consul 和 Vault 紧密协作,分别负责服务发现和密钥管理,这使得 Nomad 更加轻量级,调度性能更高。

    🚀 下面我们将通过基于 Consul 自动建立 Nomad 集群的方式,快速搭建一个 Nomad 集群。

    1. Consul 集群启动

    🚀 首先,我们需要启动一个 Consul 集群。在之前的文章中,我们已经详细介绍了 Consul 集群的搭建方法。这里,我们直接列出步骤,不再赘述。

    💻 在每个节点上下载 Consul 1.4.4 版本:

    # wget -c https://releases.hashicorp.com/consul/1.4.4/consul_1.4.4_linux_amd64.zip
    # unzip consul_1.4.4_linux_amd64.zip
    # cp consul /usr/local/bin
    # consul -v

    🚀 启动 Consul 集群:

    # nohup consul agent -server -ui -dns-port=53 -bootstrap-expect=3 -data-dir=~/.bin/consul-install/consul-data -node=consul-1 -client=0.0.0.0 -bind=172.16.66.102 -datacenter=dc1 > consul-1.log & 2>&1
    # nohup consul agent -server -ui -dns-port=53  -bootstrap-expect=3 -data-dir=/root/consul-install/consul-data -node=consul-2 -client=0.0.0.0 -bind=172.16.66.103 -datacenter=dc1 -join 172.16.66.102 > consul-2.log & 2>&1
    # nohup consul agent -server -ui -dns-port=53  -bootstrap-expect=3 -data-dir=/root/consul-install/consul-data -node=consul-3 -client=0.0.0.0 -bind=172.16.66.104 -datacenter=dc1 -join 172.16.66.102 > consul-3.log & 2>&1

    👀 验证 Consul 集群是否启动成功:

    # consul members
    # consul operator raft list-peers

    2. DNS 设置(可选)

    🌐 如果采用基于 Consul DNS 的服务发现方式,则需要在每个 Nomad client 节点上设置 DNS。

    💻 在每个节点上创建和编辑 /etc/resolvconf/resolv.conf.d/base 文件,添加以下内容:

    nameserver {consul-1-ip}
    nameserver {consul-2-ip}

    🚀 重启 resolvconf 服务:

    # /etc/init.d/resolvconf restart

    3. 基于 Consul 集群引导启动 Nomad 集群

    🚀 在每个节点上下载 Nomad:

    # wget -c https://releases.hashicorp.com/nomad/0.8.7/nomad_0.8.7_linux_amd64.zip
    # unzip nomad_0.8.7_linux_amd64.zip.zip
    # cp ./nomad /usr/local/bin
    # nomad -v

    💻 创建 Nomad 配置文件 agent.hcl

    // agent.hcl
    
    data_dir = "/root/.bin/nomad-install/nomad.d"
    
    server {
      enabled = true
      bootstrap_expect = 3
    }
    
    client {
      enabled = true
    }

    🚀 启动 Nomad 集群:

    # nohup nomad agent -config=/root/.bin/nomad-install/agent.hcl  > nomad-1.log & 2>&1
    # nohup nomad agent -config=/root/.bin/nomad-install/agent.hcl  > nomad-2.log & 2>&1
    # nohup nomad agent -config=/root/.bin/nomad-install/agent.hcl  > nomad-3.log & 2>&1

    👀 验证 Nomad 集群是否启动成功:

    # nomad server members
    # nomad operator raft list-peers
    # nomad node-status

    📊 Nomad 还提供一个 UI 界面 (http://nomad-node-ip:4646/ui),可以直观地查看 Nomad 集群的状态,包括 server、clients、工作负载 (job) 的情况。

    二. 部署工作负载:轻量级调度,高效运行

    🏗️ Nomad 通过 Job 文件来描述工作负载,并使用 Job 相关子命令来完成所有关于工作负载的操作。

    🚀 下面我们将使用一个简单的 HTTP 后端服务示例,演示如何使用 Nomad 部署工作负载。

    1. Job 文件定义

    💻 创建 httpbackend-1.nomad Job 文件:

    // httpbackend-1.nomad
    
    job "httpbackend" {
      datacenters = ["dc1"]
      type = "service"
    
      group "httpbackend" {
        count = 2
    
        task "httpbackend" {
          driver = "docker"
          config {
            image = "bigwhite/httpbackendservice:v1.0.0"
            port_map {
              http = 8081
            }
            logging {
              type = "json-file"
            }
          }
    
          resources {
            network {
              mbits = 10
              port "http" {}
            }
          }
    
          service {
            name = "httpbackend"
            port = "http"
          }
        }
      }
    }

    💡 Job 文件定义了 Job、Group 和 Task 的层次关系。

    • Job: 描述一个工作负载,例如一个 HTTP 后端服务。
    • Group: 包含一组要放在同一个集群中调度的 Task。
    • Task: 由其驱动程序 (driver) 在 Nomad client 节点上执行的命令、服务、应用程序或其他工作负载。

    2. Job 计划与执行

    🚀 使用 nomad job plan 命令进行 dry-run,检查 Job 文件格式是否正确,以及 Nomad 集群是否有足够的资源来创建和调度新的工作负载:

    # nomad job plan httpbackend-1.nomad

    🚀 使用 nomad job run 命令正式创建和调度 Job:

    # nomad job run httpbackend-1.nomad

    3. Job 状态查看

    👀 使用 nomad job status 命令查看 Job 的创建情况以及某个 Job 的详细状态信息:

    # nomad job status

    三. 总结:轻量级、灵活、高效

    🎉 Nomad 提供了一个轻量级、灵活、高效的集群管理和微服务部署调度方案,它可以帮助我们快速搭建和管理集群,并轻松部署和调度各种工作负载。

    💡 与 Kubernetes 相比,Nomad 的学习曲线更低,部署和维护成本更低,同时它也拥有足够的灵活性,能够满足各种场景下的需求。

    🚀 在云原生时代,Nomad 作为一种轻量级利器,将为我们提供更便捷、更高效的集群管理和微服务部署调度体验。

    参考文献

    1. 使用nomad实现集群管理和微服务部署调度 | Tony Bai

    😊 希望本文能够帮助你更好地理解和使用 Nomad!

  • CLOG:Common Lisp 万能 GUI – 踏入 Web GUI 世界之旅

    CLOG:Common Lisp 万能 GUI – 踏入 Web GUI 世界之旅

    🚀 引言:CLOG – Lisp 驱动的 Web GUI 框架

    想象一个世界,你可以用 Common Lisp 的优雅和表达能力构建强大的跨平台图形用户界面 (GUI)。这就是 CLOG,Common Lisp 万能 GUI,所带来的承诺。CLOG 利用 Web 技术的力量,创建交互式、动态的 GUI,模糊了传统 GUI 框架和 Web 开发之间的界限。

    CLOG 不仅仅是一个框架;它是一个通往思考 GUI 开发新方式的桥梁。它允许你构建可以在本地或远程运行的应用程序,无缝地适应不同的平台和设备。把它想象成连接 Lisp 世界与庞大的 Web 技术生态系统的桥梁。

    📚 入门:踏入 CLOG 宇宙的第一步

    CLOG 的设计既适合经验丰富的 Lisp 程序员,也适合新手。要开始,你需要安装 CLOG 和 Quicklisp(Common Lisp 的包管理器)。

    💡 CLOG REPL:你的交互式游乐场

    安装完 CLOG 后,你可以沉浸在 CLOG REPL 的交互式世界中。这个 REPL 充当你的指挥中心,允许你用 CLOG 命令进行实验,并在浏览器窗口中看到结果。

    💻 构建你的第一个 CLOG 应用程序

    让我们从一个简单的 “Hello World!” 示例开始:

    (ql:quickload :clog)
    (clog:clog-repl)
    (in-package clog-user)
    (create-div *body* :content "Hello World!")

    这段代码做了以下事情:

    1. 加载 CLOG 包。
    2. 启动 CLOG REPL,打开一个浏览器窗口。
    3. 进入 clog-user 包,提供对 CLOG 函数的访问。
    4. 在浏览器窗口的 *body* 中创建一个 div 元素,显示 “Hello World!”

    🚀 CLOG 事件:与你的 GUI 交互

    CLOG 提供了一个全面的事件系统,允许你响应用户交互,例如点击、鼠标移动和键盘输入。

    例如,以下是如何创建一个按钮,当点击时改变其背景颜色:

    (let ((tmp (create-button *body* :content "Click Me")))
      (set-on-click tmp
        (lambda (obj)
          (setf (background-color tmp) :red))))

    这段代码:

    1. 创建一个按钮。
    2. 定义一个函数,将按钮的背景颜色更改为红色。
    3. 将此函数作为按钮的 on-click 处理程序附加。

    🌐 CLOG 和 Web 技术:强大的合作关系

    CLOG 与 HTML、CSS 和 JavaScript 等 Web 技术无缝集成。你可以使用这些熟悉的工具来设计和样式化你的 CLOG 应用程序,创建视觉上吸引人和交互式的界面。

    💡 CLOG 对象:构建 GUI 的积木

    CLOG 提供了一组丰富的对象,代表各种 GUI 元素,例如按钮、文本字段、图像等等。这些对象经过精心设计,映射到它们的 HTML 对应物,允许你创建复杂而精致的用户界面。

    🚀 CLOG:未来框架

    CLOG 是一个强大的框架,为 GUI 开发打开了无限的可能性。它利用 Web 技术的能力、直观的 Lisp 语法和对交互性的关注,使其成为构建现代跨平台应用程序的引人注目的选择。

    📚 参考资料

    🎉 加入 CLOG 革命

    CLOG 是一个充满活力且不断发展的社区。加入讨论版,探索用 Lisp 进行 Web GUI 开发的世界。GUI 开发的未来就在这里,它由 CLOG 提供支持!

人生梦想 - 关注前沿的计算机技术 acejoy.com