-
Notifications
You must be signed in to change notification settings - Fork 26
/
13-函数.texi
1750 lines (1225 loc) · 81.6 KB
/
13-函数.texi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\input texinfo @c -*- texinfo -*-
@c %**start of header
@setfilename 13-函数.info
@settitle
@documentencoding UTF-8
@documentlanguage en
@c %**end of header
@finalout
@titlepage
@title
@author zunrong
@end titlepage
@contents
@ifnottex
@node Top
@top
@end ifnottex
@menu
* 13 函数::
@detailmenu
--- The Detailed Node Listing ---
13 函数
* 13.1 什么是函数?: 131 什么是函数?.
* 13.2 Lambda 表达式: 132 Lambda 表达式.
* 13.3 命名函数: 133 命名函数.
* 13.4 定义函数: 134 定义函数.
* 13.5 调用函数: 135 调用函数.
* 13.6 映射函数: 136 映射函数.
* 13.7 匿名函数: 137 匿名函数.
* 13.8 泛型函数: 138 泛型函数.
* 13.9 访问函数单元格内容: 139 访问函数单元格内容.
* 13.10 闭包: 1310 闭包.
* 13.11 建议 Emacs Lisp 函数: 1311 建议 Emacs Lisp 函数.
* 13.12 声明过时的函数: 1312 声明过时的函数.
* 13.13 内联函数: 1313 内联函数.
* 13.14 declare形式: 1314 declare形式.
* 13.15 告诉编译器定义了一个函数: 1315 告诉编译器定义了一个函数.
* 13.16 判断一个函数是否可以安全调用: 1316 判断一个函数是否可以安全调用.
* 13.17 其他与函数相关的话题: 1317 其他与函数相关的话题.
13.2 Lambda 表达式
* 13.2.1 Lambda 表达式的组成部分: 1321 Lambda 表达式的组成部分.
* 13.2.2 一个简单的 Lambda 表达式示例: 1322 一个简单的 Lambda 表达式示例.
* 13.2.3 参数列表的特点: 1323 参数列表的特点.
* 13.2.4 函数的文档字符串: 1324 函数的文档字符串.
13.11 建议 Emacs Lisp 函数
* 13.11.1 操纵建议的原语: 13111 操纵建议的原语.
* 13.11.2 建议命名函数: 13112 建议命名函数.
* 13.11.3 编写建议的方法: 13113 编写建议的方法.
* 13.11.4 使用旧的 defadvice 适配代码: 13114 使用旧的 defadvice 适配代码.
@end detailmenu
@end menu
@node 13 函数
@chapter 13 函数
Lisp 程序主要由 Lisp 函数组成。本章解释什么是函数,它们如何接受参数,以及如何定义它们。
@menu
* 13.1 什么是函数?: 131 什么是函数?.
* 13.2 Lambda 表达式: 132 Lambda 表达式.
* 13.3 命名函数: 133 命名函数.
* 13.4 定义函数: 134 定义函数.
* 13.5 调用函数: 135 调用函数.
* 13.6 映射函数: 136 映射函数.
* 13.7 匿名函数: 137 匿名函数.
* 13.8 泛型函数: 138 泛型函数.
* 13.9 访问函数单元格内容: 139 访问函数单元格内容.
* 13.10 闭包: 1310 闭包.
* 13.11 建议 Emacs Lisp 函数: 1311 建议 Emacs Lisp 函数.
* 13.12 声明过时的函数: 1312 声明过时的函数.
* 13.13 内联函数: 1313 内联函数.
* 13.14 declare形式: 1314 declare形式.
* 13.15 告诉编译器定义了一个函数: 1315 告诉编译器定义了一个函数.
* 13.16 判断一个函数是否可以安全调用: 1316 判断一个函数是否可以安全调用.
* 13.17 其他与函数相关的话题: 1317 其他与函数相关的话题.
@end menu
@node 131 什么是函数?
@section 13.1 什么是函数?
在一般意义上,函数是在给定输入值(称为参数)的情况下执行计算的规则。计算的结果称为函数的值或返回值。计算也可能有副作用,例如变量值或数据结构内容的持久变化(请参阅副作用的定义)。纯函数是一个函数,除了没有副作用之外,它总是为相同的参数组合返回相同的值,而不管外部因素如机器类型或系统状态如何。
在大多数计算机语言中,每个函数都有一个名称。但是在 Lisp 中,最严格意义上的函数没有名称:它是一个对象,可以选择与用作函数名称的符号(例如,汽车)相关联。请参阅命名函数。当一个函数被命名时,我们通常也将该符号称为 @code{函数} (例如,我们称为 @code{函数汽车} )。在本手册中,函数名称和函数对象本身之间的区别通常并不重要,但我们会注意任何相关的地方。
某些类似函数的对象,称为特殊形式和宏,也接受参数来执行计算。但是,如下所述,这些在 Emacs Lisp 中不被视为函数。
以下是函数和类函数对象的重要术语:
@lisp
lambda expression
@end lisp
用 Lisp 编写的函数(严格意义上,即函数对象)。这些将在下一节中描述。请参阅 Lambda 表达式。
@lisp
primitive ¶
@end lisp
一个可从 Lisp 调用但实际上是用 C 编写的函数。基元也称为内置函数或 subrs。示例包括 car 和 append 等函数。此外,所有特殊形式(见下文)也被视为原语。
通常,函数被实现为原语,因为它是 Lisp 的基本部分(例如,汽车),或者因为它为操作系统服务提供低级接口,或者因为它需要快速运行。与 Lisp 中定义的函数不同,只能通过更改 C 源代码和重新编译 Emacs 来修改或添加原语。请参阅编写 Emacs 基元。
@lisp
special form
@end lisp
类似于函数但不以通常方式评估其所有参数的原语。它可能只评估一些参数,或者可能以不寻常的顺序或多次评估它们。示例包括 if、and 和 while。见特殊表格。
@lisp
macro
@end lisp
Lisp 中定义的构造,它与函数的不同之处在于它将 Lisp 表达式转换为另一个要计算的表达式,而不是原始表达式。宏使 Lisp 程序员可以做特殊形式可以做的事情。请参阅宏。
@lisp
command ¶
@end lisp
可以通过命令执行原语调用的对象,通常是由于用户键入绑定到该命令的键序列。请参阅交互式呼叫。一个命令通常是一个函数; 如果函数是用 Lisp 编写的,则在函数定义中通过交互形式将其制成命令(参见定义命令)。作为函数的命令也可以从 Lisp 表达式中调用,就像其他函数一样。
键盘宏(字符串和向量)也是命令,即使它们不是函数。请参阅键盘宏。如果一个符号的功能单元包含一个命令,我们就说它是一个命令(参见符号组件); 可以使用 Mx 调用这样的命名命令。
@lisp
closure
@end lisp
一个与 lambda 表达式非常相似的函数对象,不同之处在于它还包含了一个词法变量绑定的环境。请参阅闭包。
@lisp
byte-code function
@end lisp
已由字节编译器编译的函数。请参阅字节码函数类型。
@lisp
autoload object ¶
@end lisp
实际功能的占位符。如果调用了自动加载对象,Emacs 会加载包含真实函数定义的文件,然后调用真实函数。请参阅自动加载。
您可以使用函数 functionp 来测试一个对象是否是一个函数:
@lisp
Function: functionp object ¶
@end lisp
如果 object 是任何类型的函数,即可以传递给 funcall,则此函数返回 t。请注意,对于作为函数名称的符号,functionp 返回 t,对于特殊形式返回 nil。
还可以找出任意函数需要多少个参数:
@lisp
Function: func-arity function ¶
@end lisp
此函数提供有关指定函数的参数列表的信息。返回值是 (min . max) 形式的 cons 单元格,其中 min 是参数的最小数量,max 是参数的最大数量,或者是带有 &rest 参数的函数的符号 many,或者符号 unevalled if函数是一种特殊形式。
请注意,此函数在某些情况下可能会返回不准确的结果,例如:
@itemize
@item
使用部分应用定义的函数(参见部分应用)。
@item
使用建议添加建议的函数(请参阅建议命名函数)。
@item
动态确定参数列表的函数,作为其代码的一部分。
@end itemize
与 functionp 不同,接下来的三个函数不将符号视为其函数定义。
@lisp
Function: subrp object ¶
@end lisp
如果 object 是内置函数(即 Lisp 原语),则此函数返回 t。
@lisp
(subrp 'message) ; message is a symbol,
⇒ nil ; not a subr object.
(subrp (symbol-function 'message))
⇒ t
@end lisp
@lisp
Function: byte-code-function-p object ¶
@end lisp
如果 object 是字节码函数,则此函数返回 t。例如:
@lisp
(byte-code-function-p (symbol-function 'next-line))
⇒ t
@end lisp
@lisp
Function: subr-arity subr ¶
@end lisp
这类似于 func-arity,但仅适用于内置函数并且没有符号间接。它表示非内置函数的错误。我们建议改用 func-arity。
@node 132 Lambda 表达式
@section 13.2 Lambda 表达式
lambda 表达式是用 Lisp 编写的函数对象。这是一个例子:
@lisp
(lambda (x)
"Return the hyperbolic cosine of X."
(* 0.5 (+ (exp x) (exp (- x)))))
@end lisp
在 Emacs Lisp 中,这样的列表是一个有效的表达式,它的计算结果是一个函数对象。
lambda 表达式本身没有名称。它是一个匿名函数。尽管可以以这种方式使用 lambda 表达式(请参阅匿名函数),但它们更常与符号相关联以生成命名函数(请参阅命名函数)。在进入这些细节之前,以下小节将描述 lambda 表达式的组件及其作用。
@menu
* 13.2.1 Lambda 表达式的组成部分: 1321 Lambda 表达式的组成部分.
* 13.2.2 一个简单的 Lambda 表达式示例: 1322 一个简单的 Lambda 表达式示例.
* 13.2.3 参数列表的特点: 1323 参数列表的特点.
* 13.2.4 函数的文档字符串: 1324 函数的文档字符串.
@end menu
@node 1321 Lambda 表达式的组成部分
@subsection 13.2.1 Lambda 表达式的组成部分
lambda 表达式是一个如下所示的列表:
@lisp
(lambda (arg-variables…)
[documentation-string]
[interactive-declaration]
body-forms…)
@end lisp
lambda 表达式的第一个元素始终是符号 lambda。这表明该列表表示一个函数。将函数定义为以 lambda 开头的原因是,其他用于其他用途的列表不会意外地作为函数有效。
第二个元素是符号列表——参数变量名称(参见参数列表的特性)。这称为 lambda 列表。当调用 Lisp 函数时,参数值将与 lambda 列表中的变量进行匹配,这些变量具有提供的值的本地绑定。请参阅局部变量。
文档字符串是放置在函数定义中的 Lisp 字符串对象,用于描述 Emacs 帮助工具的函数。请参阅函数的文档字符串。
交互式声明是一个表单列表(交互式代码字符串)。如果以交互方式使用函数,这声明了如何提供参数。具有此声明的函数称为命令; 它们可以使用 Mx 调用或绑定到一个键。不打算以这种方式调用的函数不应具有交互式声明。请参阅定义命令,了解如何编写交互式声明。
其余元素是函数的主体:完成函数工作的 Lisp 代码(或者,正如 Lisp 程序员所说, @code{要评估的 Lisp 表单列表} )。函数返回的值是body最后一个元素的返回值。
@node 1322 一个简单的 Lambda 表达式示例
@subsection 13.2.2 一个简单的 Lambda 表达式示例
考虑以下示例:
@lisp
(lambda (a b c) (+ a b c))
@end lisp
我们可以通过将其传递给 funcall 来调用此函数,如下所示:
@lisp
(funcall (lambda (a b c) (+ a b c))
1 2 3)
@end lisp
此调用计算 lambda 表达式的主体,其中变量 a 绑定到 1,b 绑定到 2,c 绑定到 3。对主体的求值将这三个数字相加,产生结果 6; 因此,此函数调用返回值 6。
请注意,参数可以是其他函数调用的结果,如下例所示:
@lisp
(funcall (lambda (a b c) (+ a b c))
1 (* 2 3) (- 5 4))
@end lisp
这将从左到右评估参数 1、(* 2 3) 和 (- 5 4)。然后它将 lambda 表达式应用于参数值 1、6 和 1 以产生值 8。
正如这些示例所示,您可以使用带有 lambda 表达式作为其 CAR 的表单来创建局部变量并为其赋值。在过去的 Lisp 时代,这种技术是绑定和初始化局部变量的唯一方法。但如今,为此目的使用特殊形式 let 更加清晰(参见局部变量)。Lambda 表达式主要用作匿名函数以作为参数传递给其他函数(请参阅匿名函数),或存储为符号函数定义以生成命名函数(请参阅命名函数)。
@node 1323 参数列表的特点
@subsection 13.2.3 参数列表的特点
我们的简单示例函数 (lambda (abc) (+ abc)) 指定了三个参数变量,因此必须用三个参数调用它:如果你试图只用两个参数或四个参数调用它,你会得到一个错误的数字-of-arguments 错误(请参阅错误)。
编写一个允许省略某些参数的函数通常很方便。例如,函数 substring 接受三个参数——字符串、开始索引和结束索引——但如果省略第三个参数,则默认为字符串的长度。某些函数也可以方便地接受不定数量的参数,就像函数 list 和 + 所做的那样。
要指定在调用函数时可以省略的可选参数,只需在可选参数之前包含关键字 &optional 即可。要指定零个或多个额外参数的列表,请在最后一个参数之前包含关键字 &rest。
因此,参数列表的完整语法如下:
@lisp
(required-vars…
[&optional [optional-vars…]]
[&rest rest-var])
@end lisp
方括号表示 &optional 和 &rest 子句以及它们后面的变量是可选的。
对函数的调用需要每个必需变量的一个实际参数。可能有零个或多个可选变量的实际参数,除此之外不能有任何实际参数,除非 lambda 列表使用 &rest。在这种情况下,可能有任意数量的额外实际参数。
如果省略了可选变量和剩余变量的实际参数,则它们始终默认为 nil。该函数无法区分 nil 的显式参数和省略的参数。但是,函数体可以自由地将 nil 视为其他一些有意义值的缩写。这就是子字符串的作用; nil 作为 substring 的第三个参数意味着使用提供的字符串的长度。
Common Lisp 注意:Common Lisp 允许函数指定在省略可选参数时使用的默认值; Emacs Lisp 总是使用 nil。Emacs Lisp 不支持提供的-p 变量来告诉您参数是否被显式传递。
例如,如下所示的参数列表:
@lisp
(a b &optional c d &rest e)
@end lisp
将 a 和 b 绑定到前两个实际参数,这是必需的。如果提供了一个或两个以上参数,则 c 和 d 分别绑定到它们; 前四个之后的任何参数都被收集到一个列表中,并且 e 绑定到该列表。因此,如果只有两个参数,c、d 和 e 为零; 如果两个或三个参数,d 和 e 为零; 如果四个参数或更少,e 为零。请注意,恰好为 e 提供了具有显式 nil 参数的五个参数将导致该 nil 参数作为具有一个元素 (nil) 的列表传递,与 e 的任何其他单个值一样。
没有办法在可选参数后面加上必需的参数——这是没有意义的。要了解为什么必须如此,假设示例中的 c 是可选的,而 d 是必需的。假设给出了三个实际参数; 第三个参数用于哪个变量? 它将用于 c 还是 d? 人们可以为这两种可能性争论不休。同样,在 &rest 参数之后再添加任何参数(必需的或可选的)也没有任何意义。
以下是参数列表和正确调用的一些示例:
@lisp
(funcall (lambda (n) (1+ n)) ; One required:
1) ; requires exactly one argument.
⇒ 2
(funcall (lambda (n &optional n1) ; One required and one optional:
(if n1 (+ n n1) (1+ n))) ; 1 or 2 arguments.
1 2)
⇒ 3
(funcall (lambda (n &rest ns) ; One required and one rest:
(+ n (apply '+ ns))) ; 1 or more arguments.
1 2 3 4 5)
⇒ 15
@end lisp
@node 1324 函数的文档字符串
@subsection 13.2.4 函数的文档字符串
lambda 表达式可以选择在 lambda 列表之后有一个文档字符串。该字符串不影响函数的执行; 它是一种注释,是一种系统化的注释,它实际上出现在 Lisp 世界中,并且可以被 Emacs 帮助工具使用。请参阅文档,了解如何访问文档字符串。
为程序中的所有函数提供文档字符串是一个好主意,即使是那些仅从程序中调用的函数。文档字符串类似于注释,只是它们更易于访问。
文档字符串的第一行应该独立存在,因为 apropos 只显示第一行。它应该由一两个完整的句子组成,总结了函数的目的。
文档字符串的开头通常在源文件中缩进,但由于这些空格位于起始双引号之前,它们不是字符串的一部分。有些人习惯于缩进字符串的任何其他行,以便文本在程序源中对齐。这是一个错误。以下行的缩进在字符串内部; 当帮助命令显示时,源代码中看起来不错的东西看起来很难看。
您可能想知道文档字符串如何是可选的,因为它后面有函数的必需组件(主体)。由于字符串的评估返回该字符串,没有任何副作用,如果它不是正文中的最后一个形式,则它没有任何效果。因此,在实践中,正文的第一种形式和文档字符串之间没有混淆; 如果唯一的主体形式是一个字符串,那么它既可以用作返回值,也可以用作文档。
文档字符串的最后一行可以指定不同于实际函数参数的调用约定。像这样写文本:
@lisp
\(fn arglist)
@end lisp
在行首的空行之后,文档字符串中没有换行符。('\' 用于避免混淆 Emacs 运动命令。)以这种方式指定的调用约定出现在帮助消息中,代替从函数的实际参数派生的调用约定。
此功能对宏定义特别有用,因为宏定义中编写的参数通常与用户对宏调用部分的看法不符。
如果您想弃用调用约定并支持您按上述规范宣传的调用约定,请不要使用此功能。相反,使用advertised-calling-convention 声明(参见声明表单)或set-advertised-calling-convention(参见声明过时函数),因为这两个将导致字节编译器在编译Lisp程序时发出警告消息已弃用的调用约定。
@node 133 命名函数
@section 13.3 命名函数
符号可以作为函数的名称。当符号的函数单元(参见符号组件)包含函数对象(例如,lambda 表达式)时,就会发生这种情况。然后符号本身成为一个有效的、可调用的函数,相当于其函数单元格中的函数对象。
函数单元格的内容也称为符号的函数定义。使用符号的函数定义代替符号的过程称为符号函数间接; 请参阅符号函数间接。如果你没有给符号一个函数定义,那么它的函数单元就被称为是无效的,并且它不能被用作一个函数。
在实践中,几乎所有函数都有名称,并通过它们的名称来引用。您可以通过定义 lambda 表达式并将其放入函数单元格来创建命名 Lisp 函数(请参阅访问函数单元格内容)。但是,更常见的是使用 defun 特殊形式,将在下一节中介绍。请参阅定义函数。
我们给函数命名是因为在 Lisp 表达式中通过它们的名称来引用它们很方便。此外,一个命名的 Lisp 函数可以很容易地引用它自己——它可以是递归的。此外,原语只能通过它们的名称在文本中引用,因为原语函数对象(请参阅原语函数类型)没有读取语法。
函数不需要有唯一的名称。一个给定的函数对象通常只出现在一个符号的函数单元格中,但这只是一种约定。使用 fset 很容易将其存储在多个符号中; 那么每个符号都是同一函数的有效名称。
请注意,用作函数名的符号也可以用作变量; 符号的这两种用法是独立的,并不冲突。(在某些 Lisp 方言中,情况并非如此,例如 Scheme。)
按照惯例,如果一个函数的符号由两个用 @code{--} 分隔的名称组成,则该函数是供内部使用的,第一部分命名定义该函数的文件。例如,名为 vc-git--rev-parse 的函数是 vc-git.el 中定义的内部函数。用 C 编写的内部使用函数的名称以 @code{-internal} 结尾,例如 bury-buffer-internal。2018 年之前贡献的 Emacs 代码可能遵循其他内部使用的命名约定,这些约定正在逐步淘汰。
@node 134 定义函数
@section 13.4 定义函数
我们通常在首次创建函数时为其命名。这称为定义函数,我们通常使用 defun 宏来完成。本节还介绍了定义函数的其他方法。
@lisp
Macro: defun name args [doc] [declare] [interactive] body… ¶
@end lisp
defun 是定义新的 Lisp 函数的常用方法。它将符号名称定义为具有参数列表 args 的函数(请参阅参数列表的特征)和 body 给出的主体形式。name 和 args 都不应该被引用。
doc,如果存在,应该是一个字符串,指定函数的文档字符串(请参阅函数文档字符串)。如果存在,则声明应该是指定函数元数据的声明表单(请参阅声明表单)。交互,如果存在,应该是一个交互形式,指定如何交互调用函数(参见交互调用)。
defun 的返回值是未定义的。
这里有些例子:
@lisp
(defun foo () 5)
(foo)
⇒ 5
(defun bar (a &optional b &rest c)
(list a b c))
(bar 1 2 3 4 5)
⇒ (1 2 (3 4 5))
(bar 1)
⇒ (1 nil nil)
(bar)
error→ Wrong number of arguments.
(defun capitalize-backwards ()
"Upcase the last letter of the word at point."
(interactive)
(backward-word 1)
(forward-word 1)
(backward-char 1)
(capitalize-word 1))
@end lisp
注意不要无意中重新定义现有功能。defun 甚至毫不犹豫地重新定义了汽车等原始功能。Emacs 不会阻止你这样做,因为重新定义一个函数有时是故意的,没有办法区分故意的重新定义和无意的重新定义。
@lisp
Function: defalias name definition &optional doc ¶
@end lisp
该函数将符号名称定义为一个函数,带有定义定义(可以是任何有效的 Lisp 函数)。它的返回值是未定义的。
如果 doc 不为 nil,则成为 name 的函数文档。否则,将使用定义提供的任何文档。
在内部,defalias 通常使用 fset 来设置定义。但是,如果 name 具有 defalias-fset-function 属性,则关联的值将用作函数来代替 fset 调用。
使用 defalias 的正确位置是定义特定函数名称的地方——尤其是该名称显式出现在正在加载的源文件中的地方。这是因为 defalias 记录了哪个文件定义了函数,就像 defun 一样(参见卸载)。
相比之下,在为其他目的操作函数定义的程序中,最好使用 fset,它不会保留此类记录。请参阅访问函数单元格内容。
您不能使用 defun 或 defalias 创建新的原始函数,但您可以使用它们来更改任何符号的函数定义,即使是诸如 car 或 x-popup-menu 之类的正常定义为原始符号的符号。然而,这是有风险的:例如,在不完全破坏 Lisp 的情况下重新定义汽车几乎是不可能的。重新定义诸如 x-popup-menu 之类的晦涩功能的危险性较小,但它仍然可能无法按您预期的那样工作。如果从 C 代码调用原语,它们会直接调用原语的 C 定义,因此更改符号的定义不会对它们产生影响。
另见 defsubst,它定义了一个类似于 defun 的函数,并告诉 Lisp 编译器对其执行内联扩展。请参阅内联函数。
要取消定义函数名称,请使用 fmakunbound。请参阅访问函数单元格内容。
@node 135 调用函数
@section 13.5 调用函数
定义功能只是成功的一半。函数在您调用它们之前不会做任何事情,即告诉它们运行。调用函数也称为调用。
调用函数的最常见方法是评估列表。例如,评估列表 (concat "a" "b") 调用带有参数 "a" 和 "b" 的函数 concat。有关评估的说明,请参阅评估。
当您在程序中将列表编写为表达式时,您可以在程序的文本中指定要调用的函数以及要为其提供多少参数。通常这正是你想要的。有时您需要在运行时计算要调用的函数。为此,请使用函数 funcall。当您还需要在运行时确定要传递多少个参数时,请使用 apply。
@lisp
Function: funcall function &rest arguments ¶
@end lisp
funcall 使用参数调用函数,并返回函数返回的任何内容。
由于 funcall 是一个函数,因此它的所有参数,包括函数,都会在调用 funcall 之前进行评估。这意味着您可以使用任何表达式来获取要调用的函数。这也意味着 funcall 不会看到您为参数编写的表达式,而只会看到它们的值。在调用函数的行为中,这些值不会被第二次评估; funcall 的操作就像调用函数的正常过程一样,一旦它的参数已经被评估。
参数函数必须是 Lisp 函数或原始函数。不允许使用特殊形式和宏,因为它们只有在给定未计算的参数表达式时才有意义。funcall 无法提供这些,因为正如我们在上面看到的,它从一开始就永远不知道它们。
如果您需要使用 funcall 来调用命令并使其表现得像交互式调用一样,请使用 funcall-interactively(请参阅交互式调用)。
@lisp
(setq f 'list)
⇒ list
(funcall f 'x 'y 'z)
⇒ (x y z)
(funcall f 'x 'y '(z))
⇒ (x y (z))
(funcall 'and t nil)
error→ Invalid function: #<subr and>
@end lisp
将这些示例与 apply 的示例进行比较。
@lisp
Function: apply function &rest arguments ¶
@end lisp
apply 使用参数调用函数,就像 funcall 但有一个区别:最后一个参数是对象列表,它们作为单独的参数而不是单个列表传递给函数。我们说 apply 扩展这个列表,以便每个单独的元素成为一个参数。
带有单个参数的 apply 是特殊的:参数的第一个元素必须是一个非空列表,它作为一个函数调用,其余元素作为单独的参数。传递两个或更多参数会更快。
apply 返回调用函数的结果。与 funcall 一样,函数必须是 Lisp 函数或原始函数; 特殊形式和宏在 apply 中没有意义。
@lisp
(setq f 'list)
⇒ list
(apply f 'x 'y 'z)
error→ Wrong type argument: listp, z
(apply '+ 1 2 '(3 4))
⇒ 10
(apply '+ '(1 2 3 4))
⇒ 10
(apply 'append '((a b c) nil (x y z) nil))
⇒ (a b c x y z)
(apply '(+ 3 4))
⇒ 7
@end lisp
有关使用 apply 的有趣示例,请参阅 mapcar 的定义。
有时将函数的某些参数固定为某些值是很有用的,而将其余参数留给函数实际调用时使用。固定一些函数参数的行为称为函数的部分应用。结果是一个新函数,它接受其余参数并调用原始函数并将所有参数组合在一起。
以下是如何在 Emacs Lisp 中执行部分应用程序:
@lisp
Function: apply-partially func &rest args ¶
@end lisp
此函数返回一个新函数,当调用该函数时,将调用 func 并使用由 args 和调用时指定的附加参数组成的参数列表。如果 func 接受 n 个参数,那么使用 m <= n 个参数调用 apply-partially 将产生一个具有 n - m 个参数的新函数12。
下面是我们如何定义内置函数 1+,如果它不存在,使用 apply-partially 和 +,另一个内置函数 13:
@lisp
(defalias '1+ (apply-partially '+ 1)
"Increment argument by one.")
(1+ 10)
⇒ 11
@end lisp
Lisp 函数通常接受函数作为参数或在数据结构中找到它们(尤其是在钩子变量和属性列表中)并使用 funcall 或 apply 调用它们。接受函数参数的函数通常称为函数。
有时,当您调用函数时,提供一个无操作函数作为参数很有用。这里有两种不同的无操作函数:
@lisp
Function: identity argument ¶
@end lisp
此函数返回参数并且没有副作用。
@lisp
Function: ignore &rest arguments ¶
@end lisp
此函数忽略任何参数并返回 nil。
@lisp
Function: always &rest arguments ¶
@end lisp
此函数忽略任何参数并返回 t。
有些函数是用户可见的命令,可以交互调用(通常通过按键序列)。通过使用 call-interactively 函数,可以完全调用这样的命令,就好像它被交互式调用一样。请参阅交互式呼叫。
脚注
(11)
这与 currying 相关但不同,currying 将接受多个参数的函数转换为可以作为函数链调用的函数,每个函数都有一个参数。
(12)
如果 func 可以接受的参数数量是无限的,那么新函数也将接受无限数量的参数,因此在这种情况下 apply-partially 不会减少新函数可以接受的参数数量。
(13)
请注意,与内置函数不同,此版本接受任意数量的参数。
@node 136 映射函数
@section 13.6 映射函数
映射函数将给定函数(不是特殊形式或宏)应用于列表或其他集合的每个元素。Emacs Lisp 有几个这样的函数; 本节介绍 mapcar、mapc、mapconcat 和 mapcan,它们在列表上进行映射。有关映射 obarray 中符号的函数 mapatoms,请参见 mapatoms 的定义。有关映射哈希表中键/值关联的函数 maphash,请参见 maphash 的定义。
这些映射函数不允许使用字符表,因为字符表是一个稀疏数组,其标称索引范围非常大。要以适当处理其稀疏性质的方式映射 char-table,请使用函数 map-char-table(请参阅 Char-Tables)。
@lisp
Function: mapcar function sequence ¶
@end lisp
mapcar 依次对序列的每个元素应用函数,并返回结果列表。
参数序列可以是除字符表之外的任何类型的序列; 即列表、向量、布尔向量或字符串。结果始终是一个列表。结果的长度与序列的长度相同。例如:
@lisp
(mapcar #'car '((a b) (c d) (e f)))
⇒ (a c e)
(mapcar #'1+ [1 2 3])
⇒ (2 3 4)
(mapcar #'string "abc")
⇒ ("a" "b" "c")
;; Call each function in my-hooks.
(mapcar 'funcall my-hooks)
(defun mapcar* (function &rest args)
"Apply FUNCTION to successive cars of all ARGS.
Return the list of results."
;; If no list is exhausted,
(if (not (memq nil args))
;; apply function to CARs.
(cons (apply function (mapcar #'car args))
(apply #'mapcar* function
;; Recurse for rest of elements.
(mapcar #'cdr args)))))
(mapcar* #'cons '(a b c) '(1 2 3 4))
⇒ ((a . 1) (b . 2) (c . 3))
@end lisp
@lisp
Function: mapcan function sequence ¶
@end lisp
此函数将函数应用于序列的每个元素,如 mapcar,但不是将结果收集到列表中,而是通过更改结果(使用 nconc;请参阅重新排列列表的函数)。与 mapcar 一样,序列可以是除字符表之外的任何类型。
@lisp
;; Contrast this:
(mapcar #'list '(a b c d))
⇒ ((a) (b) (c) (d))
;; with this:
(mapcan #'list '(a b c d))
⇒ (a b c d)
@end lisp
@lisp
Function: mapc function sequence ¶
@end lisp
mapc 与 mapcar 类似,只是该函数仅用于副作用——它返回的值被忽略,而不是收集到列表中。mapc 总是返回序列。
@lisp
Function: mapconcat function sequence separator ¶
@end lisp
mapconcat 将函数应用于序列的每个元素; 结果,必须是字符序列(字符串、向量或列表),被连接成单个字符串返回值。在每对结果序列之间,mapconcat 从分隔符插入字符,分隔符也必须是字符串、向量或字符列表。请参阅序列、数组和向量。
参数函数必须是一个可以接受一个参数并返回一系列字符的函数:字符串、向量或列表。参数序列可以是除字符表之外的任何类型的序列; 即列表、向量、布尔向量或字符串。
@lisp
(mapconcat #'symbol-name
'(The cat in the hat)
" ")
⇒ "The cat in the hat"
(mapconcat (lambda (x) (format "%c" (1+ x)))
"HAL-8000"
"")
⇒ "IBM.9111"
@end lisp
@node 137 匿名函数
@section 13.7 匿名函数
尽管函数通常同时使用 defun 和给定名称定义,但有时使用显式 lambda 表达式(匿名函数)会很方便。匿名函数在函数名所在的地方都是有效的。它们通常被分配为变量值,或作为函数的参数; 例如,您可以将一个作为函数参数传递给 mapcar,该函数将该函数应用于列表的每个元素(请参阅映射函数)。请参阅 describe-symbols 示例,了解一个实际的示例。
在定义用作匿名函数的 lambda 表达式时,原则上可以使用任何方法来构造列表。但通常你应该使用 lambda 宏,或者特殊形式的函数,或者 #' 读取语法:
@lisp
Macro: lambda args [doc] [interactive] body… ¶
@end lisp
此宏返回一个匿名函数,其中包含参数列表 args、文档字符串 doc(如果有)、交互式规范交互(如果有)和 body 给出的正文形式。
在动态绑定下,此宏有效地使 lambda 表单自引用:评估 CAR 为 lambda 的表单会产生表单本身:
@lisp
(lambda (x) (* x x))
⇒ (lambda (x) (* x x))
@end lisp
请注意,在词法绑定下进行评估时,结果是一个闭包对象(请参阅闭包)。
lambda 形式还有另一个效果:它通过将函数用作子例程(见下文)告诉 Emacs 求值器和字节编译器它的参数是一个函数。
@lisp
Special Form: function function-object ¶
@end lisp
这种特殊形式返回函数对象而不对其进行评估。在这方面,它类似于引用(参见引用)。但与引用不同的是,它还可以作为 Emacs 评估器和字节编译器的注释,说明函数对象旨在用作函数。假设 function-object 是一个有效的 lambda 表达式,这有两个效果:
当代码被字节编译时,函数对象被编译成字节码函数对象(参见字节编译)。
当启用词法绑定时,函数对象被转换为闭包。请参阅闭包。
当函数对象是一个符号并且代码是字节编译时,如果该函数未定义或在运行时可能不知道,字节编译器将发出警告。
读取语法 #' 是使用函数的简写。以下形式都是等价的:
@lisp
(lambda (x) (* x x))
(function (lambda (x) (* x x)))
#'(lambda (x) (* x x))
@end lisp
在下面的示例中,我们定义了一个 change-property 函数,该函数将一个函数作为其第三个参数,然后是一个双属性函数,该函数通过向其传递一个匿名函数来使用 change-property:
@lisp
(defun change-property (symbol prop function)
(let ((value (get symbol prop)))
(put symbol prop (funcall function value))))
(defun double-property (symbol prop)
(change-property symbol prop (lambda (x) (* 2 x))))
@end lisp
请注意,我们不引用 lambda 形式。
如果编译上面的代码,匿名函数也会被编译。如果您通过将匿名函数引用为列表来构造匿名函数,则不会发生这种情况:
@lisp
(defun double-property (symbol prop)
(change-property symbol prop '(lambda (x) (* 2 x))))
@end lisp
在这种情况下,匿名函数将作为 lambda 表达式保存在编译的代码中。字节编译器不能假定这个列表是一个函数,即使它看起来像一个,因为它不知道 change-property 打算将它用作一个函数。
@node 138 泛型函数
@section 13.8 泛型函数
使用 defun 定义的函数对其参数的类型和预期值有一组硬编码假设。例如,如果使用任何其他类型的值(例如向量或字符串)调用其参数值(数字或数字列表)的函数,该函数将失败或发出错误信号。发生这种情况是因为函数的实现没有准备好处理设计期间假定的类型以外的类型。
相比之下,面向对象的程序使用多态函数:一组具有相同名称的专用函数,每个函数都是为一组特定的参数类型编写的。实际调用哪个函数是在运行时根据实际参数的类型决定的。
Emacs 提供对多态性的支持。与其他 Lisp 环境一样,尤其是 Common Lisp 及其 Common Lisp 对象系统 (CLOS),这种支持基于通用函数。Emacs 泛型函数紧跟 CLOS,包括使用相似的名称,所以如果您有 CLOS 的经验,本节的其余部分听起来会非常熟悉。
泛型函数通过定义其名称和参数列表来指定抽象操作,但(通常)没有实现。几个特定类的参数的实际实现由方法提供,这些方法应该单独定义。实现泛型函数的每个方法都与泛型函数具有相同的名称,但是方法的定义通过专门化泛型函数定义的参数来指示它可以处理哪些类型的参数。这些论点专家可能或多或少是具体的。例如,字符串类型比更一般的类型(如序列)更具体。
请注意,与基于消息的 OO 语言(例如 C++ 和 Simula)不同,实现泛型函数的方法不属于一个类,它们属于它们实现的泛型函数。
调用泛型函数时,它通过将调用者传递的实际参数与每个方法的参数专用器进行比较来选择适用的方法。如果调用的实际参数与方法的专用程序兼容,则该方法适用。如果有不止一种方法适用,则使用某些规则将它们组合在一起,如下所述,然后组合处理调用。
@lisp
Macro: cl-defgeneric name arguments [documentation] [options-and-methods…] &rest body ¶
@end lisp
此宏定义具有指定名称和参数的通用函数。如果 body 存在,它提供默认实现。如果存在文档(应该总是存在),它会以 (:documentation docstring) 的形式指定通用函数的文档字符串。可选的选项和方法可以是以下形式之一:
@lisp
(:method [qualifiers…] args &rest body)
@end lisp
声明表格,如声明表格中所述。
@lisp
(:argument-precedence-order &rest args)
@end lisp
这种形式会影响组合适用方法的排序顺序。通常,在组合过程中比较两个方法时,从左到右检查方法参数,并且参数专门化器更具体的第一个方法将排在另一个之前。这种形式定义的顺序会覆盖它,并且根据它们在这种形式中的顺序检查参数,而不是从左到右。
@lisp
(:method [qualifiers…] args &rest body)
@end lisp
这种形式定义了一个类似 cl-defmethod 的方法。
@lisp
Macro: cl-defmethod name [extra] [qualifier] arguments [&context (expr spec)…] &rest [docstring] body ¶
@end lisp
该宏定义了名为 name 的通用函数的特定实现。实现代码由 body 给出。如果存在,则 docstring 是该方法的文档字符串。参数列表在实现泛型函数的所有方法中必须相同,并且必须与该函数的参数列表匹配,提供形式为 (arg spec) 的参数专用器,其中 arg 是在 cl 中指定的参数名称-defgeneric 调用,而 spec 是以下特殊形式之一:
@lisp
type
@end lisp
此专用程序要求参数为给定类型,是下面描述的类型层次结构中的类型之一。
@lisp
(eql object)
@end lisp
此专门工具要求参数是给定对象的 eql。
@lisp
(head object)
@end lisp
参数必须是一个 cons 单元格,其 car 是 eql 到 object。
@lisp
struct-type
@end lisp
参数必须是使用 cl-defstruct 定义的名为 struct-type 的类的实例(请参阅 GNU Emacs Lisp 的 Common Lisp Extensions 中的结构)或其子类之一。
方法定义可以使用新的参数列表关键字 &context,它引入了额外的专门工具,在方法运行时测试环境。此关键字应出现在必需参数列表之后,但在任何 &rest 或 &optional 关键字之前。&context 专用器看起来很像常规参数专用器(expr spec),除了 expr 是要在当前上下文中评估的表达式,而 spec 是要比较的值。例如,&context (overwrite-mode (eql t)) 将使该方法仅在打开 overwrite-mode 时适用。&context 关键字后面可以跟任意数量的上下文特化器。因为上下文特化器不是泛型函数的参数签名的一部分,所以它们可以在不需要它们的方法中被省略。
类型专用器 (arg type) 可以指定以下列表中的系统类型之一。当指定父类型时,类型是其更具体的子类型中的任何一个的参数,以及孙子、孙子孙等也将是兼容的。
@lisp
integer
@end lisp
父类型:数字。
@lisp
number
@end lisp
@lisp
null
@end lisp
父类型:符号
@lisp
symbol
@end lisp
@lisp
string
@end lisp
父类型:数组。
@lisp
array
@end lisp
父类型:序列。
@lisp
cons
@end lisp
父类型:列表。
@lisp
list
@end lisp
父类型:序列。
@lisp
marker
@end lisp
@lisp
overlay
@end lisp
@lisp
float
@end lisp
父类型:数字。
@lisp
window-configuration
@end lisp
@lisp
process
@end lisp
@lisp
window
@end lisp
@lisp
subr
@end lisp
@lisp
compiled-function
@end lisp
@lisp
buffer
@end lisp
@lisp
char-table
@end lisp
父类型:数组。
@lisp
bool-vector
@end lisp
父类型:数组。
@lisp
vector
@end lisp
父类型:数组。
@lisp
frame
@end lisp
@lisp
hash-table
@end lisp
@lisp
font-spec
@end lisp
@lisp
font-entity
@end lisp
@lisp
font-object
@end lisp
可选的额外元素,表示为 ':extra string',允许您为相同的专用符和限定符添加更多方法,以字符串区分。
可选限定符允许组合几种适用的方法。如果不存在,则定义的方法是主要方法,负责为专用参数提供泛型函数的主要实现。您还可以使用以下值之一作为限定符来定义辅助方法:
@lisp
:before
@end lisp
此辅助方法将在主要方法之前运行。更准确地说,所有 :before 方法都将在主要方法之前以最具体的优先顺序运行。
@lisp
:after
@end lisp
此辅助方法将在主要方法之后运行。更准确地说,所有这些方法都将在主要方法之后以最具体的最后顺序运行。
@lisp
:around
@end lisp
此辅助方法将代替主要方法运行。最具体的此类方法将在任何其他方法之前运行。此类方法通常使用 cl-call-next-method(如下所述)来调用其他辅助或主要方法。
使用 cl-defmethod 定义的函数不能通过向它们添加交互形式来实现交互,即命令(参见定义命令)。如果您需要多态命令,我们建议定义一个普通命令,该命令调用通过 cl-defgeneric 和 cl-defmethod 定义的多态函数。
每次调用泛型函数时,它都会通过组合为函数定义的适用方法来构建将处理此调用的有效方法。寻找适用方法并产生有效方法的过程称为调度。适用的方法是那些其所有特工都与调用的实际参数兼容的方法。由于所有参数都必须与专用器兼容,因此它们都决定了方法是否适用。显式特化多个参数的方法称为多分派方法。
适用的方法按它们组合的顺序排序。最左边的参数专门化器是最具体的方法将按顺序排在第一位。(指定 :argument-precedence-order 作为 cl-defmethod 的一部分会覆盖它,如上所述。)如果方法主体调用 cl-call-next-method,则将运行下一个最具体的方法。如果有适用的 :around 方法,它们中最具体的将首先运行; 它应该调用 cl-call-next-method 来运行任何不太具体的 :around 方法。接下来,:before 方法按照它们的特殊性顺序运行,然后是主要方法,最后是 :after 方法,按照它们特殊性的相反顺序运行。
@lisp
Function: cl-call-next-method &rest args ¶
@end lisp
当从主方法或 :around 辅助方法的词法体中调用时,为同一个泛型函数调用下一个适用的方法。通常,它被调用时不带参数,这意味着使用与调用方法相同的参数来调用下一个适用的方法。否则,将使用指定的参数。
@lisp
Function: cl-next-method-p ¶
@end lisp
当从主方法或 :around 辅助方法的词法体中调用此函数时,如果有下一个方法要调用,则返回非 nil。
@node 139 访问函数单元格内容
@section 13.9 访问函数单元格内容
符号的函数定义是存储在符号的函数单元中的对象。此处描述的功能访问、测试和设置符号的功能单元。
另见函数间接函数。请参见间接函数的定义。
@lisp
Function: symbol-function symbol ¶
@end lisp
这将返回符号函数单元格中的对象。它不检查返回的对象是否是合法函数。
如果函数单元格为 void,则返回值为 nil。要区分为 void 的函数单元格和设置为 nil 的函数单元格,请使用 fboundp(见下文)。
@lisp
(defun bar (n) (+ n 2))
(symbol-function 'bar)
⇒ (lambda (n) (+ n 2))
(fset 'baz 'bar)
⇒ bar
(symbol-function 'baz)
⇒ bar
@end lisp
如果您从未给符号任何函数定义,我们说该符号的函数单元格是无效的。换句话说,函数单元格中没有任何 Lisp 对象。如果您尝试将符号作为函数调用,Emacs 会发出 void-function 错误信号。
请注意,void 与 nil 或符号 void 不同。符号 nil 和 void 是 Lisp 对象,并且可以像任何其他对象一样存储到函数单元中(如果您依次使用 defun 定义它们,它们可以是有效函数)。空函数单元格不包含任何对象。
您可以使用 fboundp 测试符号函数定义的无效性。给符号定义函数后,可以使用 fmakunbound 再次使其无效。
@lisp
Function: fboundp symbol ¶
@end lisp
如果符号在其函数单元格中有对象,则此函数返回 t,否则返回 nil。它不检查对象是否是合法函数。
@lisp
Function: fmakunbound symbol ¶
@end lisp
此函数使符号的函数单元格无效,因此随后尝试访问此单元格将导致无效函数错误。它返回符号。(另见 makunbound,在当变量为空时。)
@lisp
(defun foo (x) x)
(foo 1)
⇒1
(fmakunbound 'foo)
⇒ foo
(foo 1)
error→ Symbol's function definition is void: foo
@end lisp
@lisp
Function: fset symbol definition ¶
@end lisp
该函数将定义存储在符号的函数单元中。结果是定义。通常定义应该是一个函数或一个函数的名称,但这不被检查。参数符号是一个普通的评估参数。
此函数的主要用途是作为定义或更改函数的构造的子例程,例如 defun 或advice-add(请参阅Advising Emacs Lisp Functions)。您还可以使用它为符号提供不是函数的函数定义,例如键盘宏(请参阅键盘宏):
@lisp
;; Define a named keyboard macro.
(fset 'kill-two-lines "\^u2\^k")
⇒ "\^u2\^k"
@end lisp
如果您希望使用 fset 为函数创建备用名称,请考虑改用 defalias。请参见defalias 的定义。
@node 1310 闭包
@section 13.10 闭包
正如变量绑定的范围规则中所解释的,Emacs 可以选择启用变量的词法绑定。启用词法绑定后,您创建的任何命名函数(例如,使用 defun)以及您使用 lambda 宏或函数特殊形式或 #' 语法(请参阅匿名函数)创建的任何匿名函数都会自动转换为闭包。
闭包是一个函数,它还带有定义函数时存在的词法环境的记录。当它被调用时,其定义中的任何词法变量引用都使用保留的词法环境。在所有其他方面,闭包的行为很像普通函数。特别是,它们可以像普通函数一样被调用。
有关使用闭包的示例,请参见词法绑定。