(car 《为自己写本-Guile-书》)
前言中,我说要写一个文式编程工具。它的名字叫 zero,是个命令行程序,运行时需要由使用者提供一些参数与文式编程元文档路径。zero 读取元文档,然后根据使用者设定的参数对元文档进行处理,最终给出相应的输出。本章内容主要讲述如何用 Guile 写一个命令行程序的界面——对于使用者而言,zero 程序可见的部分。
分割命令行文本C 程序可以通过 main
函数的参数获得命令行文本的分割结果,即一个字符串数组:
/* foo.c */
#include
int
main(int argc, char **argv) {for (int i = 0; i
设编译所得程序为 foo,执行它,
$ ./foo bar foobar
可得
./foo
bar
foobar
用 Guile 语言也能写出类似的程序:
;; foo.scm
(define (display-args args)(cond ((null? args) #nil)(else (begin(display (car args)) (newline)(display-args (cdr args))))))
(display-args (command-line))
需要用 Guile 解释器来运行这个程序:
$ guile foo.scm bar foobar
程序运行结果为:
foo.scm
bar
foobar
如果在上述 Guile 代码的首部增加
#!/usr/bin/guile -s
!#
然后将 foo.scm
文件改名为 foo
,并使之具备可执行权限:
$ chomd +x ./foo
这样,这个 Guile 脚本程序在行为上与上述 C 程序完全一样。
现在,假设 C 语言未提供 for
与 while
循环(迭代)结构,那么使用函数对自身的调用来模拟迭代过程,可以写出与上述 Guile 代码相似的形式:
#include
void
display_args(char **args, int i, int n) {if (i >= n) {return;} else {printf("%s\n", args[i]);display_args(args, i + 1, n);}
}
int
main(int argc, char **argv) {display_args(argv, 0, argc);return 0;
}
如果将 argv
转换为一个以 NULL
为结尾的字符串数组,便可以让 C 语言版的 display_args
在形式上很像 Guile 版的 display-args
函数:
#include
#include
#include
void
display_args(char **args) {if (*args == NULL) {return;} else {printf("%s\n", *args);display_args(args + 1);}
}
int
main(int argc, char **argv) {char **new_argv = malloc((argc + 1) * sizeof(char *));memcpy(new_argv, argv, argc * sizeof(char *));new_argv[argc] = NULL;display_args(new_argv);free(new_argv);return 0;
}
上文中的 Guile 代码,经过 C 代码的诠释,可观其大略——用函数的递归形式模拟了 C 的循环结构。至于代码中的一些细节,后文逐一给出解释。
列表,见其首,不见其尾在 C 程序中,命令行文本是保存在 main
函数的 argv
参数中的,这个参数是个字符串数组。在 Guile 脚本中,命令行文本是通过函数 command-line
函数在运行时获取的,即
(command-line)
该函数的返回结果是一个字符串列表。这行代码便是 command-line
函数的调用代码。command-line
函数不需要参数,对它的调用,可用下面这行 C 代码来诠释:
command-line(); /* 伪代码,因为 C 语言不支持这种函数命名方式 */
那么,command-line
函数的返回结果——字符串列表是怎样的一种数据结构?答案是,不清楚。我们只知道,它是列表类型的数据。
在 Guile 中,对于所有的列表类型的数据,使用 car
函数可以从列表中取出首个元素;使用 cdr
函数可以从列表中取出除首个元素之外的所有元素,所取出的元素构成一个新的列表,并且这些元素在新列表中的次序与原列表相同。
下面这份 Guile 脚本:
;; demo-01.scm
#!/usr/bin/guile -s
!#
(display (car (command-line)))
(newline)
(display (cdr (command-line)))
(newline)
(display (car (cdr (command-line))))
(newline)
执行它:
$ ./demo-01.scm foo bar foobar
得到的结果依序如下:
./demo-01.scm
(foo bar foobar)
foo
如果看不懂上述 Guile 代码,可以看下面的等效的伪 C 代码:
printf("%s", car(command-line()));
printf("\n");
printf("%s", cdr(command-line()));
printf("\n");
printf("%s", car(cdr(command-line())));
printf("\n");
通过这些等效的伪 C 代码,可以理解 Guile 函数的调用方式,以及 display
与 newline
函数的效用。
下面这段 Guile 代码
(cond ((null? args) #nil)(else (begin(display (car args)) (newline)(display-args (cdr args))))))
与之等效的 C 代码如下:
if (*args == NULL) {return;
} else {printf("%s\n", *args);display_args(args + 1);
}
cond
是 condition
的缩写,其用法如下:
(cond (<谓词 1> <表达式 1>)(<谓词 2> <表达式 2>)... ...(<谓词 n> <表达式 n>)(else <表达式 n &#43; 1>)
等效的 C 条件表达式结构如下&#xff1a;
if (<谓词 1>) {<表达式 1>
} else if (<谓词 1>) {
} else if (...) {...
} else if (<谓词 n>) {<表达式 n>
} else {<表达式 n &#43; 1>
}
所谓的谓词是指可以返回『真』或『假』的计算过程。(null? args)
便是 Guile 的一个谓词——如果 args
列表非空&#xff0c;它返回『假』&#xff0c;否则返回『真』。
下面这个条件表达式
(cond ((null? args) #nil)(else (car args)))
它表达的意思是&#xff0c;如果列表 args
为空&#xff0c;那么这个条件表达式的计算结果为 #nil
——空的列表&#xff0c;否则计算结果为 args
的首元素。
cond
表达式中&#xff0c;对各个条件分支中的谓词是按顺序求值的。在这个过程中&#xff0c;如果某个谓词的求知结果为真&#xff0c;那么该谓词之后的表达式的求值结果便是 cond
表达式的求值结果。
有时&#xff0c;我们需要无条件的依序执行一些计算过程&#xff0c;例如&#xff1a;
(display (car args))
(newline)
(display-args (cdr args))
这在 C 语言里是很平淡无奇的过程&#xff0c;但是 Guile 语言却不能直接支持&#xff0c;因为它的任何语句都必须是一条完整的表达式&#xff0c;而不能使多个独立的表达式的陈列。为了能够依序执行一组表达式&#xff0c;可以用 begin
语句&#xff1a;
(begin <表达式 1> <表达式 2> ... <表达式 n>)
<表达式 n>
的求值结果是 begin
语句的求值结果。
下面这条 begin
语句&#xff1a;
(begin(display (car args)) (newline)(display-args (cdr args)))
它的含义应该很明显了。
函数下面这些代码&#xff0c;除了 args
之外&#xff0c;其他元素都是确定的&#xff0c;这意味着 args
是个未知数或变量。
(cond ((null? args) #nil)(else (begin(display (car args)) (newline)(display-args (cdr args)))))
如果一个未知的事物与一些确定的事物之间存在着确定的联系&#xff0c;这些联系可以将未知的事物转换为另一个未知的事物&#xff0c;这个过程就是所谓的『映射』或『函数』。在 Guile 中&#xff0c;定义一个函数需要遵守下面这样的格式&#xff1a;
(define (<函数> <未知的事物>) <未知的事物与一些确定的事物之间所存在的确定的联系>)
前文中&#xff0c;我们已经定义了一个函数 display-args
&#xff1a;
(define (display-args args)(cond ((null? args) #nil)(else (begin(display (car args)) (newline)(display-args (cdr args))))))
函数 y &#61; f(x)
&#xff0c;如果我们已知 x &#61; 2
&#xff0c;那么根据 f(2)
就可以得到相应的 y
值。在 C 语言中&#xff0c;这叫函数调用。在 Guile 中&#xff0c;这叫函数应用。没必要在这些文字游戏上浪费时间&#xff0c;本质上就是将确定的自变量 x &#61; 2
代入 y &#61; f(x)
这个函数或映射&#xff0c;从而得到确定的因变量。在编程中&#xff0c;我们通常将自变量称为参数&#xff0c;将因变量称为返回值。这其实都是玩弄文字的把戏……
有些函数是没有求值结果的&#xff0c;例如 display
函数&#xff0c;它的任务是将用户传入的参数显示于终端&#xff08;显示器屏幕或文件&#xff09;。这类似于&#xff0c;你给朋友一些钱&#xff0c;让他去书店为你买本书&#xff0c;这本书是『你朋友从你哪里接过钱&#xff0c;然后去书店买书』这个过程的『求值结果』&#xff0c;但是你给一个画家一些钱&#xff0c;让他在人民公园的墙上为你涂鸦&#xff0c;结果你得到了什么&#xff1f;可能是他人的驻足围观&#xff0c;也可能是公园管理人员给你开罚单……
对于 display-args
函数而言&#xff0c;如果它的参数是列表类型&#xff0c;那么它总是有求值结果的&#xff0c;即 #nil
&#xff0c;但是它除了可以得到这个结果&#xff0c;在其执行过程中还不断的在终端中涂鸦……也就是说 display-args
是个有副作用的函数。它的副作用是 display
函数带来的。
数学家们不喜欢有副作用的函数&#xff0c;因为他们是数学家。他们喜欢的那种编程语言&#xff0c;叫做『纯函数式编程语言』。像 C 语言这种到处都充满着副作用的编程语言&#xff0c;他们是非常非常的拒绝的&#xff0c;他们讨厌 x &#61; x - 1
这样的代码&#xff0c;因为他们认为 0 &#61; -1
这样的推导结果是荒谬的。想必他们对现实世界也非常的不习惯吧&#xff0c;他们从药瓶里倒出一粒药吃下去&#xff0c;然后他们得到了两个药瓶 :D
如果像下面这样应用 display-args
函数&#xff1a;
(display-args (cons 1 (cons 2 (cons 3 #nil))))
可以得到什么结果&#xff1f;可以得到 #nil
&#xff0c;同时终端中会显示&#xff1a;
1
2
3
(cons 1 (cons 2 (cons 3 #nil)))
是什么&#xff1f;它是一连串 cons
运算符的应用。如果将 cons
视为一个函数&#xff0c;那么等效的 C 代码如下&#xff1a;
cons(1, cons(2, cons(3, #nil)));
结果是一个列表&#xff0c;其元素依次为 1, 2, 3。将这个列表传入 display-args
&#xff0c;便会将其元素逐一显示于终端。
cons
运算符的第一个参数可以是任意类型的数据&#xff0c;而它的第二个参数必须是列表类型。它的工作是&#xff0c;将第一个参数所表示的数据添加到第二个参数所表示的列表的首部&#xff0c;然后返回这个新的列表。上文中说过&#xff0c;#nil
表示空的列表。(cons 3, #nil)
可将 3
添加到一个空的列表的首部&#xff0c;返回一个新的列表——只含有元素 3 的列表。以此类推&#xff0c;(cons 2 (cons 3 #nil))
的结果是依序包含 2
与 3
的列表&#xff0c;(cons 1 (cons 2 (cons 3 #nil)))
的结果是依序包含 1
, 2
, 3
的列表。
zero 程序的用法如下&#xff1a;
$ zero [选项] 文件
zero 程序可以支持以下选项&#xff1a;
-m, --mode&#61;moon 或 sun 指定 zero 的工作模式是 moon 还是 sun
-e, --entrance&#61;代码块 将指定的代码块设为代码的抽取入口
-o, --output&#61;文件 将提取到的代码输出至指定的文件
-b, --backtrace 开启代码反向定位功能
-p, --prism&#61;棱镜程序 为 sun 模式指定一个棱镜程序
由于这些选项在形式上大同小异&#xff0c;因此下面仅以 -m
与 --mode
选项为例&#xff0c;讲述如何为 zero 程序构造一个简单的命令行界面。-m
选项为短选项&#xff0c;--mode
为长选项&#xff0c;它们是同一个选项的两种表现形式。也就是说&#xff0c;下面这两行代码是等价的&#xff1a;
$ zero -m moon foo.zero
$ zero --mode&#61;moon foo.zero
要构建的这个命令行界面程序的主要任务是&#xff0c;从命令行文本中获取 -m
或 --mode
的参数值以及文件名。对于上面示例中的 zero
命令行文本而言&#xff0c;要获取的是 moon
与 foo.zero
。
文件名解析
(define (get-filename args)(cond ((null? (cdr args)) (car args))(else (get-filename (cdr args)))))
这个函数的求值结果为字符串类型&#xff0c;是 zero 程序要读取的文件的名字&#xff08;或路径&#xff09;。
命令行选项参数解析
由于 -m
或 -mode
选项只有两个值 moon
与 sun
可选&#xff0c;可以将它们映射为整型数&#xff1a;
参数
moon
对应 1&#xff1b;参数
sun
对应 2&#xff1b;若经过解析&#xff0c;发现命令行文本中即未出现
-m
也未出现--mode
&#xff0c;这种情况对应 0&#xff1b;若命令行文本中即出现了
-m
或--mode
&#xff0c;但是参数值既非moon
&#xff0c;亦非sun
&#xff0c;这种情况对应 -1.
根据上述映射&#xff0c;写出以下 Guile 代码&#xff1a;
#!/usr/bin/guile -s
!#
(define (filter-mode-opt args)(cond ((null? args) 0)(else (let ((fst (car args))(snd (cadr args)))(cond ((string&#61;? fst "-m")(cond ((string&#61;? snd "moon") 1)((string&#61;? snd "sun") 2)(else -1)))((string-prefix? "--mode&#61;" fst)(let ((mode (cadr (string-split fst #\&#61;))))(cond ((string&#61;? mode "moon") 1)((string&#61;? mode "sun") 2)(else -1))))(else (filter-mode-opt (cdr args))))))))
(display (filter-mode-opt (command-line)))
(newline)
上述代码中&#xff0c;出现了上文未涉及的一些语法——let
&#xff0c;cadr
&#xff0c;string-prefix?
&#xff0c;string&#61;?
&#xff0c;string-split
。这些语法的含义&#xff0c;暂时不予追究&#xff0c;先来看下面的等效 C 代码&#xff1a;
#include
#include
#include
int
filter_mode_opt(char **args) {if (*args &#61;&#61; NULL) {return 0;} else {if (strcmp(*args, "-m") &#61;&#61; 0) {char *next_arg &#61; *(args &#43; 1);if (strcmp(next_arg, "moon") &#61;&#61; 0) return 1;else if (strcmp(next_arg, "sun") &#61;&#61; 0) return 2;else return -1;} else if (strncmp(*args, "--mode", 6) &#61;&#61; 0) {int mode;char *new_arg &#61; malloc((strlen(*args) &#43; 1) * sizeof(char));strcpy(new_arg, *args);strtok(new_arg, "&#61;");char *mode_text &#61; strtok(NULL, "&#61;");if (strcmp(mode_text, "moon") &#61;&#61; 0) mode &#61; 1;else if (strcmp(mode_text, "sun") &#61;&#61; 0) mode &#61; 2;else mode &#61; -1;free(new_arg);return mode;} else {filter_mode_opt(args &#43; 1);}}
}
int
main(int argc, char **argv) {char **new_argv &#61; malloc((argc &#43; 1) * sizeof(char *));memcpy(new_argv, argv, argc * sizeof(char *));new_argv[argc] &#61; NULL;printf("%d\n", filter_mode_opt(new_argv));free(new_argv);return 0;
}
C 代码看上去要罗嗦一点&#xff0c;主要是因为 C 语言在字符串处理方面的功能弱一些&#xff0c;不过在逻辑上与上面的 Guile 代码等价。如果我们动用 for
循环&#xff0c;C 的代码反而会更清晰一些&#xff1a;
#include
#include
#include
int
filter_mode_opt(int argc, char **args) {int mode &#61; 0;for (int i &#61; 0; i
int
main(int argc, char **argv) {printf("%d\n", filter_mode_opt(argc, argv));return 0;
}
上述的 Guile 程序可以简化为&#xff1a;
#!/usr/bin/guile -s
!#
(define (which-mode? x)(cond ((string&#61;? x "moon") 1)((string&#61;? x "sun") 2)(else -1)))
(define (filter-mode-opt args)(cond ((null? args) 0)(else (let ((fst (car args))(snd (cadr args)))(cond ((string&#61;? fst "-m") (which-mode? snd))((string-prefix? "--mode&#61;" fst)(which-mode? (cadr (string-split fst #\&#61;))))(else (filter-mode-opt (cdr args))))))))
(display (filter-mode-opt (command-line)))
(newline)
同理&#xff0c;也可将 C 程序简化为&#xff1a;
#include
#include
#include
int
which_mode(char *mode_text) {if (strcmp(mode_text, "moon") &#61;&#61; 0) {return 1;} else if (strcmp(mode_text, "sun") &#61;&#61; 0) {return 2;} else {return -1;}
}
int
filter_mode_opt(int argc, char **args) {int mode &#61; 0;for (int i &#61; 0; i
int
main(int argc, char **argv) {printf("%d\n", filter_mode_opt(argc, argv));return 0;
}
现在来看一些之前未遭遇的一些细节。首先看 let
&#xff1a;
(let ((args (cons 1 (cons 2 (cons 3 #nil)))))(let ((fst (car args))(snd (car (cdr args))))(begin (display fst)(newline)(display snd)(newline))))
上述这段代码&#xff0c;经 Guile 解释器运行后&#xff0c;会输出以下结果&#xff1a;
1
2
与之大致等效的 C 代码如下&#xff1a;
#include
int
main(void) {/* 局部块 */ {int args[] &#61; {1, 2, 3, 4};/* 局部块 */ {int fst &#61; *args; /* args[0] */int snd &#61; *(args &#43; 1); /* args[1] */{printf("%d", fst);printf("\n");printf("%d", snd);printf("\n");}}}
}
也就是说&#xff0c;let
每次都能构建一个『局部环境』&#xff0c;然后定义一些局部变量以供为这个局部环境内代码使用&#xff0c;其语法结构如下&#xff1a;
(let ((<变量 1> <表达式 1>)(<变量 2> <表达式 2>)... ... ...(<变量 n> <表达式 n>))<需要使用上述变量的表达式>)
上面的 let
语句示例中&#xff0c;出现了 (car (cdr args))
这样的表达式&#xff0c;它的含义是取 args
列表的第 2 个元素。Guile 为这种操作提供了一个简化运算符 cadr
&#xff0c;用法为 (cadr args)
。同理&#xff0c;对于 (cdr (cdr args))
这样的运算&#xff0c;Guile 提供了 cddr
&#xff0c;用法为 (cddr args)
。
因为在解析命令行文本过程中&#xff0c;一些字符串运算是不可避免的。Guile 为字符串运算提供了很丰富的函数。本节中用到了 string-prefix?
&#xff0c;string&#61;?
&#xff0c;string-split
。只需通过下面几个示例便可了解它们的功能及用法。在终端中输入 guile
命令&#xff0c;进入 Guile 交互解释器环境&#xff0c;然后执行以下代码&#xff1a;
> (string-prefix? "--mode" "--mode&#61;sun")
$1 &#61; #t
> (string-prefix? "--mode" "--node&#61;sun")
$2 &#61; #f
> (string&#61;? "sun" "sun")
$3 &#61; #t
> (string&#61;? "sun" "moon")
$4 &#61; #f
> (string-split "--mode&#61;sun" #\&#61;)
$5 &#61; ("--mode" "sun")
> (string-split "--mode&#61;sun&#61;cpu" #\&#61;)
$6 &#61; ("--mode" "sun" "cpu")
在 Guile 中&#xff0c;#t
与 #f
分别表示布尔真值&#xff08;True&#xff09;与假值&#xff08;False&#xff09;&#xff0c;而 ("--mode" "sun")
与 ("--mode" "sun" "cpu")
这样结构是列表。
为每个命令行选项都像上一节中所做的那样&#xff0c;写一个专用的解析函数&#xff0c;这太过于浪费代码了。考察 filter-mode-opt
过程&#xff1a;
(define (filter-mode-opt args)(cond ((null? args) 0)(else (let ((fst (car args))(snd (cadr args)))(cond ((string&#61;? fst "-m") (which-mode? snd))((string-prefix? "--mode&#61;" fst)(which-mode? (cadr (string-split fst #\&#61;))))(else (filter-mode-opt (cdr args))))))))
在这个过程中&#xff0c;只有 -m
, --mode
以及 which-mode?
函数需要特别指定。如果将这些需要特别指定的因素作为参数传递给 filter-mode-opt
这样的函数&#xff0c;那么 filter-mode-opt
的通用性便会得到显著提升——它不仅仅能够处理 zero
的 -m
与 --mode
选项&#xff0c;只要是将选项参数映射为整数的任务&#xff0c;它都能做。这时&#xff0c;再称它为 filter-mode-opt
就不是很合理了&#xff0c;叫它 arg-to-int-parser
吧。
(define (arg-to-int-parser args short-opt long-opt text-to-int)(cond ((null? args) 0)(else (let ((fst (car args))(snd (cadr args)))(cond ((string&#61;? fst short-opt) (text-to-int snd))((string-prefix? long-opt fst)(text-to-int (cadr (string-split fst #\&#61;))))(else (arg-to-int-parser (cdr args)short-optlong-opttext-to-int)))))))
要用这个函数解析 -m
或 --mode
选项&#xff0c;只需&#xff1a;
(arg-to-int-parser (command-line) "-m" "--mode" which-mode?)
如果将 arg-to-int-parser
函数的最后一个参数 text-to-int
重命名为 map_text_into_what?
&#xff0c;然后将第一个条件分支
(null? args) 0)
改为
(null? args) (map_text_into_what? "")
然后将 "arg-to-int-parser" 重命名为 arg-parser
&#xff0c;便可得到&#xff1a;
(define (arg-parser args short-opt long-opt map-text-into-what?)(cond ((null? args) 0)(else (let ((fst (car args))(snd (cadr args)))(cond ((string&#61;? fst short-opt) (map-text-into-what? snd))((string-prefix? long-opt fst)(map-text-into-what? (cadr (string-split fst #\&#61;))))(else (arg-parser (cdr args)short-optlong-optmap-text-into-what?)))))))
只要能提供正确的 map-text-into-what?
函数&#xff0c;那么 arg-parser
函数几乎可胜任所有的命令行解析工作&#xff0c;其用法示例如下&#xff1a;
(define (which-mode? x)(cond ((string&#61;? x "") 0)((string&#61;? x "moon") 1)((string&#61;? x "sun") 2)(else -1)))
(display (arg-parser (command-line) "-m" "--mode" which-mode?))
(newline)
现在&#xff0c;等效的 C 代码已经很难写出来了&#xff0c;因为 C 语言是静态&#xff08;编译型&#xff09;语言&#xff0c;它难以实现 arg-parser
这种返回值类型是动态可变的函数。不过&#xff0c;在现实中&#xff0c;arg-parser
的返回值类型并不是太多&#xff0c;可以为每种类型定义一个 arg-parser
&#xff0c;例如&#xff1a;
int arg_parser_return_int(int argc,char **argv,char *short-opt,char *long-opt,int (*map_text_into_int)(char *));char * arg_parser_return_str(int argc,char **argv,char *short-opt,char *long-opt,char * (*map_text_into_text)(char *));
如果不畏惧指针与内存惯例&#xff0c;那么想要一个万能的 arg_parser
&#xff0c;可以用 void *
类型&#xff1a;
void * arg_parser(int argc,char **argv,char *short-opt,char *long-opt,void * (*map_text_into_int)(char *));
虽然我不会去实现这些函数&#xff0c;但是对于 void *
版本的 arg_parser
&#xff0c;我可以给出它的一个用法示例&#xff0c;即用于解析 zero 程序的 -m
或 --mode
选项&#xff1a;
int *mode &#61; arg_parser(argc, argv, "-m", "--mode", map_text_into_int);
总结
printf("%d\n", *mode);
free(mode);
C 能写的程序&#xff0c;Guile 也能写得出来&#xff0c;反之亦然。不要再说 C 能直接操作内存&#xff0c;操作硬件&#xff0c;而 Guile 不能……用 Guile 也可以模拟出内存和硬件&#xff0c;然后再操作。大致的感觉是&#xff0c;用 C 写程序&#xff0c;会觉得自己在摆弄一台小马达&#xff0c;而用 Guile 写程序&#xff0c;则觉得自己拿了根小数树枝挑拨一只毛毛虫。
Guile 语言最显著的特点有两个。第一个特点是&#xff0c;列表无处不在&#xff0c;甚至函数的定义、应用也都以列表的形式呈现的。第二个特点是&#xff0c;前缀表达式无处不在&#xff0c;正因为如此&#xff0c;我们可以在函数命名时可以使用 &#61;
&#xff0c;-
&#xff0c;?
之类的特殊符号。这两个特点是其他语言所不具备的&#xff0c;当然它也带来重重的括号。说到括号&#xff0c;可能像 Guile 这些 Scheme 系的 Lisp 风格的语言&#xff0c;它们的括号吓退了许多初学者。事实上&#xff0c;只要有个好一些的编辑器——我用的是 Emacs&#xff0c;然后动手写一些代码&#xff0c;很快就不怕了&#xff0c;甚至会感觉它们很自然。
Guile 语言在语法上未提供循环&#xff0c;初次用递归来模拟迭代&#xff0c;会有些不直观。多写写就习惯了。事实上&#xff0c;Guile 以宏的形式提供了功能强大的循环机制&#xff0c;对此以后再作介绍……其实现在我还不会用。在符合 Scheme 语言标准的前提下&#xff0c;Guile 也实现了一些属于它自己的东西。本文中用到的 #nil
以及一些字符串运算函数&#xff0c;这都是 Scheme 语言标准之外的东西。
(cdr《为自己写本-Guile-书》)