热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

(cons'(壹.命令行程序界面)《为自己写本Guile书》)

(car《为自己写本-Guile-书》)前言中,我说要写一个文式编程工具。它的名字叫zero,是个命令行程序,运行时需要由使用者提供一些参

(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 语言未提供 forwhile 循环(迭代)结构,那么使用函数对自身的调用来模拟迭代过程,可以写出与上述 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 函数的调用方式,以及 displaynewline 函数的效用。

条件表达式

下面这段 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);
}

condcondition 的缩写,其用法如下:

(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)) 的结果是依序包含 23 的列表&#xff0c;(cons 1 (cons 2 (cons 3 #nil))) 的结果是依序包含 1, 2, 3 的列表。

zero 程序界面的实现

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;要获取的是 moonfoo.zero

文件名解析

(define (get-filename args)(cond ((null? (cdr args)) (car args))(else (get-filename (cdr args)))))

这个函数的求值结果为字符串类型&#xff0c;是 zero 程序要读取的文件的名字&#xff08;或路径&#xff09;。

命令行选项参数解析

由于 -m-mode 选项只有两个值 moonsun 可选&#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-书》)




推荐阅读
  • 本文整理了315道Python基础题目及答案,帮助读者检验学习成果。文章介绍了学习Python的途径、Python与其他编程语言的对比、解释型和编译型编程语言的简述、Python解释器的种类和特点、位和字节的关系、以及至少5个PEP8规范。对于想要检验自己学习成果的读者,这些题目将是一个不错的选择。请注意,答案在视频中,本文不提供答案。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 本文介绍了2020年计算机二级MSOffice的选择习题及答案,详细解析了操作系统的五大功能模块,包括处理器管理、作业管理、存储器管理、设备管理和文件管理。同时,还解答了算法的有穷性的含义。 ... [详细]
  • 开发笔记:spring boot项目打成war包部署到服务器的步骤与注意事项
    本文介绍了将spring boot项目打成war包并部署到服务器的步骤与注意事项。通过本文的学习,读者可以了解到如何将spring boot项目打包成war包,并成功地部署到服务器上。 ... [详细]
  • 大数据Hadoop生态(20)MapReduce框架原理OutputFormat的开发笔记
    本文介绍了大数据Hadoop生态(20)MapReduce框架原理OutputFormat的开发笔记,包括outputFormat接口实现类、自定义outputFormat步骤和案例。案例中将包含nty的日志输出到nty.log文件,其他日志输出到other.log文件。同时提供了一些相关网址供参考。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 基于事件驱动的并发编程及其消息通信机制的同步与异步、阻塞与非阻塞、IO模型的分类
    本文介绍了基于事件驱动的并发编程中的消息通信机制,包括同步和异步的概念及其区别,阻塞和非阻塞的状态,以及IO模型的分类。同步阻塞IO、同步非阻塞IO、异步阻塞IO和异步非阻塞IO等不同的IO模型被详细解释。这些概念和模型对于理解并发编程中的消息通信和IO操作具有重要意义。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • centos6.8 下nginx1.10 安装 ... [详细]
author-avatar
ygluo
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有