【Elisp官方指南,翻译】2 练习求值
原文地址:https://www.gnu.org/software/emacs/manual/html_node/eintr/Practicing-Evaluation.html
备注:使用通义千文进行翻译,略微修正。
2 练习求值
在学习如何在Emacs Lisp中编写函数定义之前,花一些时间评估已写好的各种表达式是很有帮助的。这些表达式将是以函数作为其首个(且往往是唯一)元素的列表。由于与缓冲区相关的一些函数既简单又有趣,我们将从它们开始。在本节中,我们将对其中几个进行求值。在另一部分,我们将研究几个其他与缓冲区相关的函数的代码,以了解它们是如何编写的。
如何进行求值
每当您向Emacs Lisp提供编辑命令时,例如移动光标或滚动屏幕的命令,实际上就是在评估一个表达式,而这个表达式的首个元素就是一个函数。这就是Emacs的工作方式。
当您敲击键盘时,会触发Lisp解释器去评估一个表达式,并由此得到您的操作结果。即使是输入纯文本,也会涉及到评估一个Emacs Lisp函数,在这种情况下,该函数使用了self-insert-command,它仅简单地插入您键入的字符。通过按键序列调用的函数被称为交互式函数或命令;如何使函数变得可交互将在讲解如何编写函数定义的章节中予以说明。参见:使函数具有交互性。
除了通过键入键盘命令进行求值外,我们已经看到了第二种评估表达式的方法:将光标置于列表之后并键入C-x C-e。在本节剩余部分,我们将采用这种方式。还有其他方式进行表达式求值,随着学习深入我们会逐一介绍。
接下来几节中展示的函数不仅用于练习求值,它们本身也非常重要。对这些函数的研究有助于清晰理解缓冲区和文件之间的区别、如何切换到某个缓冲区以及如何确定其内部的位置。
2.1 缓冲区名称
buffer-name 和 buffer-file-name 两个函数展示了文件和缓冲区之间的区别。当您评估以下表达式时,(buffer-name),缓冲区的名称将出现在回显区域中。而当您评估 (buffer-file-name) 时,缓冲区所引用的文件名会出现在回显区域中。通常情况下,(buffer-name) 返回的名称与它所指向的文件名相同,而 (buffer-file-name) 返回的是该文件的完整路径名。
文件和缓冲区是两种不同的实体。文件是在计算机中永久记录的信息(除非您将其删除)。而缓冲区则是Emacs内部的信息,会在编辑会话结束时消失(或您删除缓冲区时)。通常情况下,缓冲区包含从文件复制过来的信息;我们说这个缓冲区正在访问那个文件。这就是您进行工作和修改的对象。对缓冲区的更改不会立即改变文件内容,直到您保存缓冲区。当您保存缓冲区时,缓冲区的内容会被复制到文件中,从而永久保存下来。
如果您正在GNU Emacs中的Info模式下阅读本文档,可以通过将光标置于每个表达式之后并键入C-x C-e来评估它们。
(buffer-name)
(buffer-file-name)
在我在Info模式下执行这些操作时,评估 (buffer-name) 的结果是 "info",而评估 (buffer-file-name) 的结果是 nil。
另一方面,在我编写这份文档时,评估 (buffer-name) 的结果是 "introduction.texinfo",而评估 (buffer-file-name) 的结果是 "/gnu/work/intro/introduction.texinfo"。
前者是缓冲区的名称,后者是文件的名称。在Info模式中,缓冲区名称是 "info"。Info并未指向任何文件,因此评估 (buffer-file-name) 的结果为nil。符号nil来自拉丁语中表示“无”的单词;在这种情况下,它表示缓冲区未关联任何文件。(在Lisp中,nil也用于表示“假”,并且是空列表(())的同义词。)
当我正在编写文档时,我的缓冲区名称是 "introduction.texinfo"。该缓冲区所指向的文件名称是 "/gnu/work/intro/introduction.texinfo"。
(在这些表达式中,括号告诉Lisp解释器将buffer-name和buffer-file-name视为函数;如果没有括号,解释器将会尝试将这些符号作为变量来求值。参见:变量章节。)
尽管文件和缓冲区之间存在区别,但人们往往会混淆两者的概念,反之亦然。实际上,大多数人会说“我在编辑一个文件”,而不是说“我在编辑一个缓冲区,不久后我会将其保存到文件中”。根据上下文,几乎总能明白人们的真实意图。然而,在处理计算机程序时,牢记这种区别很重要,因为计算机并不像人那样聪明。
顺便提一下,“缓冲区”一词来源于其作为减缓冲击力的垫子的含义。在早期的计算机中,缓冲区缓和了文件与计算机中央处理器之间的交互。存储文件的鼓或磁带以及中央处理器是运行速度不同、间歇性工作的不同设备。缓冲区使得它们能够有效地协同工作。随着时间的推移,缓冲区从一个中介、临时存储场所发展成为实际工作的场所。这种转变类似于一个小海港成长为大都市的过程:最初它只是货物暂时存放以便装载上船的地方;后来它逐渐成为一个独立的商业和文化中心。
并非所有缓冲区都与文件相关联。例如,scratch 缓冲区不访问任何文件。同样地,Help 缓冲区也不与任何文件关联。
在过去,如果用户没有~/.emacs文件,并通过只输入emacs命令而不指定任何文件启动Emacs会话,那么Emacs会以显示scratch缓冲区的方式启动。现在,您将看到一个启动屏幕。您可以按照启动屏幕上建议的命令操作,打开一个文件,或者按q键关闭启动屏幕并进入scratch缓冲区。
如果您切换到scratch缓冲区,在其中键入(buffer-name),将光标置于其后,然后键入C-x C-e来评估该表达式,返回的结果"scratch"将出现在回显区域,这是缓冲区的名称。如果您在scratch缓冲区中键入(buffer-file-name)并评估该表达式,回显区域会出现nil,这与您在Info模式下评估(buffer-file-name)时的情况相同。
顺便说一句,如果您在scratch缓冲区中想要让表达式求值结果直接出现在缓冲区内而非回显区域,可以键入C-u C-x C-e代替C-x C-e。这样会使返回的结果出现在表达式之后。缓冲区看起来就像这样:
(buffer-name)"*scratch*"
您无法在Info模式下这样做,因为Info模式是只读的,不允许您更改缓冲区的内容。但是您可以在任何可编辑的缓冲区中这样做;在编写代码或文档(比如本书)时,这个特性非常有用。
2.2 获取缓冲区
buffer-name 函数返回的是缓冲区的名称;而要获取缓冲区本身,则需要使用不同的函数:current-buffer 函数。如果在代码中使用这个函数,您将得到的是缓冲区自身。
名称和名称所指的对象或实体是不同的。您不是您的名字,而是别人用这个名字称呼的人。如果您要求找乔治谈话,有人递给您一张写着字母 'G', 'e', 'o', 'r', 'g', 和 'e' 的卡片,您可能会觉得有趣,但不会满意。您并不是想跟名字交谈,而是想与名字所指代的人交谈。缓冲区与此类似:scratch 缓冲区的名字是 scratch,但这个名字并非缓冲区本身。为了获取缓冲区自身,您需要使用 current-buffer 这样的函数。
然而,这里有一个小复杂之处:如果您像这里那样单独评估 current-buffer 表达式,看到的是缓冲区名称的打印表示形式,而不是缓冲区的内容。Emacs 之所以如此工作,有两个原因:缓冲区可能长达数千行——太长而不便于显示;另外,另一个具有相同内容但不同名称的缓冲区可能存在,区分它们很重要。
以下是一个包含该函数的表达式:
(current-buffer)
如果您以通常的方式在 Emacs 中的 Info 模式下评估此表达式,#<buffer info> 将出现在回显区域。这种特殊格式表明返回的是缓冲区自身,而不仅仅是其名称。
顺便提一下,虽然可以在程序中键入数字或符号,但对于缓冲区的打印表示形式则无法直接键入:获取缓冲区自身唯一的方法就是使用 current-buffer 这样的函数。
一个相关的函数是 other-buffer。它返回当前所在缓冲区之外最近被选择的缓冲区,而非其名称的打印表示形式。如果您刚刚从 scratch 缓冲区来回切换过,other-buffer 将返回那个缓冲区。
可以通过评估以下表达式来验证这一点:
(other-buffer)
您应该会在回显区域看到 #<buffer scratch> 出现,或者最近刚切换离开的其他缓冲区的名称。[脚注6]
脚注:
(6) 实际上,默认情况下,如果您刚刚切换离开的缓冲区在另一个窗口中对您可见,other-buffer 将会选择最近一个不可见的缓冲区;这是一个微妙之处,我经常忘记这一点。
2.3 切换缓冲区
other-buffer 函数在用作需要缓冲区作为参数的函数的实参时,实际上会提供一个缓冲区。我们可以通过将 other-buffer 与 switch-to-buffer 结合使用来切换到不同的缓冲区来观察这一点。
首先,简单介绍一下 switch-to-buffer 函数。当您在 Info 和 scratch 缓冲区之间来回切换以评估 (buffer-name) 时,很可能键入了 C-x b,然后在提示 minibuffer 输入要切换到的缓冲区名称时键入了 scratch7。按键序列 C-x b 会导致 Lisp 解释器评估交互式函数 switch-to-buffer。正如之前所说,Emacs 的工作方式是:不同的按键组合调用或运行不同的函数。例如,C-f 调用 forward-char,M-e 调用 forward-sentence 等等。
通过在表达式中编写 switch-to-buffer,并为其指定一个要切换到的缓冲区,我们可以像使用 C-x b 那样切换缓冲区:
(switch-to-buffer (other-buffer))
符号 switch-to-buffer 是列表中的首个元素,因此 Lisp 解释器会将其视为一个函数并执行与其关联的指令。但在执行前,解释器会注意到 other-buffer 在括号内,因此会首先处理这个符号。other-buffer 是该列表的第一个(在这种情况下也是唯一)元素,所以Lisp解释器会调用或运行这个函数。它会返回另一个缓冲区。接下来,解释器运行 switch-to-buffer,并将其他缓冲区作为参数传递给它,Emacs 将切换到这个缓冲区。如果您正在Info模式下阅读本文档,请现在尝试评估此表达式。(要返回,可以键入 C-x b RET。)8
在本文档后面章节的编程示例中,您将更多地看到 set-buffer 函数而不是 switch-to-buffer。这是因为计算机程序和人类之间的差异:人类有眼睛,期望在计算机终端上看到他们正在操作的缓冲区。这是如此显而易见,几乎无需多言。然而,程序没有眼睛。当计算机程序在一个缓冲区上工作时,那个缓冲区并不需要显示在屏幕上。
switch-to-buffer 是为人类设计的,做了两件不同的事情:它切换 Emacs 关注的缓冲区;并将窗口中显示的缓冲区切换到新缓冲区。相比之下,set-buffer 只做一件事情:它将计算机程序的注意力切换到不同的缓冲区。屏幕上的缓冲区保持不变(当然,在命令运行结束之前,通常不会发生任何变化)。
此外,我们刚刚引入了一个新的行话术语——“调用”。当您评估一个其首个符号是函数的列表时,您就是在调用那个函数。call 这个术语的使用源于这样的观念:函数作为一个实体,如果调用它,它可以为您做一些事情——就像水管工是一个实体,如果您打电话给他/她,他/她就能帮您修理漏水问题一样。
脚注:
(7) 或者更确切地说,为了节省输入,如果默认缓冲区是 scratch,您可能只按了 RET 键;如果默认缓冲区不同,您可能只键入部分名称,如 *sc,然后按下 TAB 键使其扩展为完整名称,最后按 RET 键。
(8) 请记住,这个表达式会让您移动到最近且不可见的其他缓冲区。如果您确实想转到最近选择的缓冲区,即使仍然可以看到它,您需要评估以下更复杂的表达式:
(switch-to-buffer (other-buffer (current-buffer) t))
在这个例子中,other-buffer 的第一个参数告诉它要跳过哪个缓冲区——当前缓冲区,第二个参数告诉 other-buffer 允许切换到可见的缓冲区。在常规使用中,switch-to-buffer 会带您前往不在窗口中可见的缓冲区,因为您很可能会使用 C-x o(other-window)快捷键去到另一个可见的缓冲区。
2.4 缓冲区大小与点的位置
最后,让我们看看几个相当简单的函数:buffer-size、point、point-min 和 point-max。这些函数提供了关于缓冲区大小以及其中光标位置(称为“点”)的信息。
函数 buffer-size 可以告诉你当前缓冲区的大小;即,该函数返回一个表示缓冲区中字符数量的计数。
(buffer-size)
您可以按照通常的方式评估这个表达式,即将光标置于表达式之后并键入 C-x C-e。
在Emacs中,光标的当前位置被称为“点”。表达式 (point) 返回一个数字,它告诉您光标所在位置距离缓冲区起始处有多少个字符。
可以通过以下方式,在本缓冲区中以通常方式评估以下表达式来查看点所在的字符计数:
(point)
在我撰写这段内容时,点的值是65724。点函数在本书后面的一些示例中频繁使用。
点的值当然取决于其在缓冲区中的位置。如果您在这个位置评估点,则得到的数字会更大:
(point)
对我来说,在这个位置上点的值为66043,这意味着在这两个表达式之间有319个字符(包括空格)。 (毫无疑问,由于我可能在首次评估点之后对内容进行了编辑,您将看到不同的数字。)
函数 point-min 与 point 类似,但它返回当前缓冲区中点允许取的最小值。如果没有启用窄化功能(Narrowing),这个值通常是1。(窄化是一种机制,可以限制自己或程序仅对缓冲区的一部分进行操作。参见“窄化与拓宽”。)同样地,函数 point-max 返回当前缓冲区中点允许取的最大值。
2.5 练习
找一个你正在编辑的文件,并将光标移动到文件中间的位置。然后,找出该文件对应的缓冲区名称、文件的实际路径名、文件的总长度以及你在文件中的当前位置。
浙公网安备 33010602011771号