(cons '(壹 . 命令行程序界面) 《爲本身寫本-Guile-書》)

(car 《爲本身寫本-Guile-書》)node

前言中,我說要寫一個文式編程工具。它的名字叫 zero,是個命令行程序,運行時須要由使用者提供一些參數與文式編程元文檔路徑。zero 讀取元文檔,而後根據使用者設定的參數對元文檔進行處理,最終給出相應的輸出。本章內容主要講述如何用 Guile 寫一個命令行程序的界面——對於使用者而言,zero 程序可見的部分。編程

分割命令行文本

C 程序能夠經過 main 函數的參數得到命令行文本的分割結果,即一個字符串數組:segmentfault

/* foo.c */
#include<stdio.h>
int
main(int argc, char **argv) {
        for (int i = 0; i < argc; i++) {
                printf("%s\n", argv[i]);
        }
        return 0;
}

設編譯所得程序爲 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<stdio.h>
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 <stdio.h>
#include <stdlib.h>
#include <string.h>
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 + 1>)

等效的 C 條件表達式結構以下:

if (<謂詞 1>) {
        <表達式 1>
} else if (<謂詞 1>) {
} else if (...) {
        ...
} else if (<謂詞 n>) {
        <表達式 n>
} else {
        <表達式 n + 1>
}

所謂的謂詞是指能夠返回『真』或『假』的計算過程。(null? args) 即是 Guile 的一個謂詞——若是 args 列表非空,它返回『假』,不然返回『真』。

下面這個條件表達式

(cond ((null? args) #nil)
      (else (car args)))

它表達的意思是,若是列表 args 爲空,那麼這個條件表達式的計算結果爲 #nil——空的列表,不然計算結果爲 args 的首元素。

順序求值

cond 表達式中,對各個條件分支中的謂詞是按順序求值的。在這個過程當中,若是某個謂詞的求知結果爲真,那麼該謂詞以後的表達式的求值結果即是 cond 表達式的求值結果。

有時,咱們須要無條件的依序執行一些計算過程,例如:

(display (car args))
(newline)
(display-args (cdr args))

這在 C 語言裏是很平淡無奇的過程,可是 Guile 語言卻不能直接支持,由於它的任何語句都必須是一條完整的表達式,而不能使多個獨立的表達式的陳列。爲了可以依序執行一組表達式,能夠用 begin 語句:

(begin <表達式 1> <表達式 2> ... <表達式 n>)

<表達式 n> 的求值結果是 begin 語句的求值結果。

下面這條 begin 語句:

(begin
  (display (car args)) (newline)
  (display-args (cdr args)))

它的含義應該很明顯了。

函數

下面這些代碼,除了 args 以外,其餘元素都是肯定的,這意味着 args 是個未知數或變量。

(cond ((null? args) #nil)
      (else (begin
              (display (car args)) (newline)
              (display-args (cdr args)))))

若是一個未知的事物與一些肯定的事物之間存在着肯定的聯繫,這些聯繫能夠將未知的事物轉換爲另外一個未知的事物,這個過程就是所謂的『映射』或『函數』。在 Guile 中,定義一個函數須要遵照下面這樣的格式:

(define (<函數> <未知的事物>) <未知的事物與一些肯定的事物之間所存在的肯定的聯繫>)

前文中,咱們已經定義了一個函數 display-args

(define (display-args args)
  (cond ((null? args) #nil)
        (else (begin
                (display (car args)) (newline)
                (display-args (cdr args))))))

函數 y = f(x),若是咱們已知 x = 2,那麼根據 f(2) 就能夠獲得相應的 y 值。在 C 語言中,這叫函數調用。在 Guile 中,這叫函數應用。不必在這些文字遊戲上浪費時間,本質上就是將肯定的自變量 x = 2 代入 y = f(x) 這個函數或映射,從而獲得肯定的因變量。在編程中,咱們一般將自變量稱爲參數,將因變量稱爲返回值。這其實都是玩弄文字的把戲……

有些函數是沒有求值結果的,例如 display 函數,它的任務是將用戶傳入的參數顯示於終端(顯示器屏幕或文件)。這相似於,你給朋友一些錢,讓他去書店爲你買本書,這本書是『你朋友從你哪裏接過錢,而後去書店買書』這個過程的『求值結果』,可是你給一個畫家一些錢,讓他在人民公園的牆上爲你塗鴉,結果你獲得了什麼?多是他人的駐足圍觀,也多是公園管理人員給你開罰單……

對於 display-args 函數而言,若是它的參數是列表類型,那麼它老是有求值結果的,即 #nil,可是它除了能夠獲得這個結果,在其執行過程當中還不斷的在終端中塗鴉……也就是說 display-args 是個有反作用的函數。它的反作用是 display 函數帶來的。

數學家們不喜歡有反作用的函數,由於他們是數學家。他們喜歡的那種編程語言,叫作『純函數式編程語言』。像 C 語言這種處處都充滿着反作用的編程語言,他們是很是很是的拒絕的,他們討厭 x = x - 1 這樣的代碼,由於他們認爲 0 = -1 這樣的推導結果是荒謬的。想必他們對現實世界也很是的不習慣吧,他們從藥瓶裏倒出一粒藥吃下去,而後他們獲得了兩個藥瓶 :D

若是像下面這樣應用 display-args 函數:

(display-args (cons 1 (cons 2 (cons 3 #nil))))

能夠獲得什麼結果?能夠獲得 #nil,同時終端中會顯示:

1
2
3

(cons 1 (cons 2 (cons 3 #nil))) 是什麼?它是一連串 cons 運算符的應用。若是將 cons 視爲一個函數,那麼等效的 C 代碼以下:

cons(1, cons(2, cons(3, #nil)));

結果是一個列表,其元素依次爲 1, 2, 3。將這個列表傳入 display-args,便會將其元素逐一顯示於終端。

cons 運算符的第一個參數能夠是任意類型的數據,而它的第二個參數必須是列表類型。它的工做是,將第一個參數所表示的數據添加到第二個參數所表示的列表的首部,而後返回這個新的列表。上文中說過,#nil 表示空的列表。(cons 3, #nil) 可將 3 添加到一個空的列表的首部,返回一個新的列表——只含有元素 3 的列表。以此類推,(cons 2 (cons 3 #nil)) 的結果是依序包含 23 的列表,(cons 1 (cons 2 (cons 3 #nil))) 的結果是依序包含 1, 2, 3 的列表。

zero 程序界面的實現

zero 程序的用法以下:

$ zero [選項] 文件

zero 程序能夠支持如下選項:

-m, --mode=moon 或 sun     指定 zero 的工做模式是 moon 仍是 sun
-e, --entrance=代碼塊       將指定的代碼塊設爲代碼的抽取入口
-o, --output=文件          將提取到的代碼輸出至指定的文件
-b, --backtrace           開啓代碼反向定位功能
-p, --prism=棱鏡程序        爲 sun 模式指定一個棱鏡程序

因爲這些選項在形式上大同小異,所以下面僅以 -m--mode 選項爲例,講述如何爲 zero 程序構造一個簡單的命令行界面。-m 選項爲短選項,--mode 爲長選項,它們是同一個選項的兩種表現形式。也就是說,下面這兩行代碼是等價的:

$ zero -m moon foo.zero
$ zero --mode=moon foo.zero

要構建的這個命令行界面程序的主要任務是,從命令行文本中獲取 -m--mode 的參數值以及文件名。對於上面示例中的 zero 命令行文本而言,要獲取的是 moonfoo.zero

文件名解析

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

這個函數的求值結果爲字符串類型,是 zero 程序要讀取的文件的名字(或路徑)。

命令行選項參數解析

因爲 -m-mode 選項只有兩個值 moonsun 可選,能夠將它們映射爲整型數:

  • 參數 moon 對應 1;

  • 參數 sun 對應 2;

  • 若通過解析,發現命令行文本中即未出現 -m 也未出現 --mode,這種狀況對應 0;

  • 若命令行文本中即出現了 -m--mode,可是參數值既非 moon,亦非 sun,這種狀況對應 -1.

根據上述映射,寫出如下 Guile 代碼:

#!/usr/bin/guile -s
!#
(define (filter-mode-opt args)
  (cond ((null? args) 0)
        (else (let ((fst (car args))
                    (snd (cadr args)))
                (cond ((string=? fst "-m")
                       (cond ((string=? snd "moon") 1)
                             ((string=? snd "sun") 2)
                             (else -1)))
                      ((string-prefix? "--mode=" fst)
                       (let ((mode (cadr (string-split fst #\=))))
                         (cond ((string=? mode "moon") 1)
                               ((string=? mode "sun") 2)
                               (else -1))))
                      (else (filter-mode-opt (cdr args))))))))
(display (filter-mode-opt (command-line)))
(newline)

上述代碼中,出現了上文未涉及的一些語法——letcadrstring-prefix?string=?string-split。這些語法的含義,暫時不予追究,先來看下面的等效 C 代碼:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int
filter_mode_opt(char **args) {
        if (*args == NULL) {
                return 0;
        } else {
                if (strcmp(*args, "-m") == 0) {
                        char *next_arg = *(args + 1);
                        if (strcmp(next_arg, "moon") == 0) return 1;
                        else if (strcmp(next_arg, "sun") == 0) return 2;
                        else return -1;
                } else if (strncmp(*args, "--mode", 6) == 0) {
                        int mode;
                        char *new_arg = malloc((strlen(*args) + 1) * sizeof(char));
                        strcpy(new_arg, *args);
                        strtok(new_arg, "=");
                        char *mode_text = strtok(NULL, "=");
                        if (strcmp(mode_text, "moon") == 0) mode = 1;
                        else if (strcmp(mode_text, "sun") == 0) mode = 2;
                        else mode = -1;
                        free(new_arg);
                        return mode;
                } else {
                        filter_mode_opt(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;
        printf("%d\n", filter_mode_opt(new_argv));
        free(new_argv);
        return 0;
}

C 代碼看上去要羅嗦一點,主要是由於 C 語言在字符串處理方面的功能弱一些,不過在邏輯上與上面的 Guile 代碼等價。若是咱們動用 for 循環,C 的代碼反而會更清晰一些:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int
filter_mode_opt(int argc, char **args) {
        int mode = 0;
        for (int i = 0; i < argc; i++) {
                if (strcmp(args[i], "-m") == 0) {
                        if (strcmp(args[i + 1], "moon") == 0) return 1;
                        else if (strcmp(args[i + 1], "sun") == 0) return 2;
                        else return -1;
                } else if (strncmp(args[i], "--mode", 6) == 0) {
                        char *new_arg = malloc((strlen(args[i]) + 1) * sizeof(char));
                        strcpy(new_arg, args[i]);
                        strtok(new_arg, "=");
                        char *mode_text = strtok(NULL, "=");
                        if (strcmp(mode_text, "moon") == 0) mode = 1;
                        else if (strcmp(mode_text, "sun") == 0) mode = 2;
                        else mode = -1;
                        free(new_arg);
                }
        }
        return mode;
}
int
main(int argc, char **argv) {
        printf("%d\n", filter_mode_opt(argc, argv));
        return 0;
}

上述的 Guile 程序能夠簡化爲:

#!/usr/bin/guile -s
!#
(define (which-mode? x)
  (cond ((string=? x "moon") 1)
        ((string=? x "sun") 2)
        (else -1)))
(define (filter-mode-opt args)
  (cond ((null? args) 0)
        (else (let ((fst (car args))
                    (snd (cadr args)))
                (cond ((string=? fst "-m") (which-mode? snd))
                      ((string-prefix? "--mode=" fst)
                       (which-mode? (cadr (string-split fst #\=))))
                      (else (filter-mode-opt (cdr args))))))))
(display (filter-mode-opt (command-line)))
(newline)

同理,也可將 C 程序簡化爲:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int
which_mode(char *mode_text) {
        if (strcmp(mode_text, "moon") == 0) {
                return 1;
        } else if (strcmp(mode_text, "sun") == 0) {
                return 2;
        } else {
                return -1;
        }
}
int
filter_mode_opt(int argc, char **args) {
        int mode = 0;
        for (int i = 0; i < argc; i++) {
                if (strcmp(args[i], "-m") == 0) mode = which_mode(args[i + 1]);
                else if (strncmp(args[i], "--mode", 6) == 0) {
                        char *new_arg = malloc((strlen(args[i]) + 1) * sizeof(char));
                        strcpy(new_arg, args[i]);
                        strtok(new_arg, "=");
                        mode = which_mode(strtok(NULL, "="));
                        free(new_arg);
                }
        }
        return mode;
}
int
main(int argc, char **argv) {
        printf("%d\n", filter_mode_opt(argc, argv));
        return 0;
}

如今來看一些以前未遭遇的一些細節。首先看 let

(let ((args (cons 1 (cons 2 (cons 3 #nil)))))
  (let ((fst (car args))
        (snd (car (cdr args))))
    (begin (display fst)
           (newline)
           (display snd)
           (newline))))

上述這段代碼,經 Guile 解釋器運行後,會輸出如下結果:

1
2

與之大體等效的 C 代碼以下:

#include <stdio.h>
int
main(void) {
        /* 局部塊 */ {
                int args[] = {1, 2, 3, 4};
                /* 局部塊 */ {
                        int fst = *args; /* args[0] */
                        int snd = *(args + 1); /* args[1] */
                        {
                                printf("%d", fst);
                                printf("\n");
                                printf("%d", snd);
                                printf("\n");
                        }
                }
        }
}

也就是說,let 每次都能構建一個『局部環境』,而後定義一些局部變量以供爲這個局部環境內代碼使用,其語法結構以下:

(let ((<變量 1> <表達式 1>)
      (<變量 2> <表達式 2>)
      ... ... ...
      (<變量 n> <表達式 n>))
  <須要使用上述變量的表達式>)

上面的 let 語句示例中,出現了 (car (cdr args)) 這樣的表達式,它的含義是取 args 列表的第 2 個元素。Guile 爲這種操做提供了一個簡化運算符 cadr,用法爲 (cadr args)。同理,對於 (cdr (cdr args)) 這樣的運算,Guile 提供了 cddr,用法爲 (cddr args)

由於在解析命令行文本過程當中,一些字符串運算是不可避免的。Guile 爲字符串運算提供了很豐富的函數。本節中用到了 string-prefix?string=?string-split。只需經過下面幾個示例即可瞭解它們的功能及用法。在終端中輸入 guile 命令,進入 Guile 交互解釋器環境,而後執行如下代碼:

> (string-prefix? "--mode" "--mode=sun")
$1 = #t
> (string-prefix? "--mode" "--node=sun")
$2 = #f
> (string=? "sun" "sun")
$3 = #t
> (string=? "sun" "moon")
$4 = #f
> (string-split "--mode=sun" #\=)
$5 = ("--mode" "sun")
> (string-split "--mode=sun=cpu" #\=)
$6 = ("--mode" "sun" "cpu")

在 Guile 中,#t#f 分別表示布爾真值(True)與假值(False),而 ("--mode" "sun")("--mode" "sun" "cpu") 這樣結構是列表。

通用過程

爲每一個命令行選項都像上一節中所作的那樣,寫一個專用的解析函數,這太過於浪費代碼了。考察 filter-mode-opt 過程:

(define (filter-mode-opt args)
  (cond ((null? args) 0)
        (else (let ((fst (car args))
                    (snd (cadr args)))
                (cond ((string=? fst "-m") (which-mode? snd))
                      ((string-prefix? "--mode=" fst)
                       (which-mode? (cadr (string-split fst #\=))))
                      (else (filter-mode-opt (cdr args))))))))

在這個過程當中,只有 -m, --mode 以及 which-mode? 函數須要特別指定。若是將這些須要特別指定的因素做爲參數傳遞給 filter-mode-opt 這樣的函數,那麼 filter-mode-opt 的通用性便會獲得顯著提高——它不只僅可以處理 zero-m--mode 選項,只要是將選項參數映射爲整數的任務,它都能作。這時,再稱它爲 filter-mode-opt 就不是很合理了,叫它 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=? fst short-opt) (text-to-int snd))
                      ((string-prefix? long-opt fst)
                       (text-to-int (cadr (string-split fst #\=))))
                      (else (arg-to-int-parser (cdr args)
                                               short-opt
                                               long-opt
                                               text-to-int)))))))

要用這個函數解析 -m--mode 選項,只需:

(arg-to-int-parser (command-line) "-m" "--mode" which-mode?)

若是將 arg-to-int-parser 函數的最後一個參數 text-to-int 重命名爲 map_text_into_what?,而後將第一個條件分支

(null? args) 0)

改成

(null? args) (map_text_into_what? "")

而後將 "arg-to-int-parser" 重命名爲 arg-parser,即可獲得:

(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=? fst short-opt) (map-text-into-what? snd))
                      ((string-prefix? long-opt fst)
                       (map-text-into-what? (cadr (string-split fst #\=))))
                      (else (arg-parser (cdr args)
                                               short-opt
                                               long-opt
                                               map-text-into-what?)))))))

只要能提供正確的 map-text-into-what? 函數,那麼 arg-parser 函數幾乎可勝任全部的命令行解析工做,其用法示例以下:

(define (which-mode? x)
  (cond ((string=? x "") 0)
        ((string=? x "moon") 1)
        ((string=? x "sun") 2)
        (else -1)))
(display (arg-parser (command-line) "-m" "--mode" which-mode?))
(newline)

如今,等效的 C 代碼已經很難寫出來了,由於 C 語言是靜態(編譯型)語言,它難以實現 arg-parser 這種返回值類型是動態可變的函數。不過,在現實中,arg-parser 的返回值類型並非太多,能夠爲每種類型定義一個 arg-parser,例如:

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 *));

若是不畏懼指針與內存慣例,那麼想要一個萬能的 arg_parser,能夠用 void * 類型:

void * arg_parser(int argc,
                  char **argv,
                  char *short-opt,
                  char *long-opt,
                  void * (*map_text_into_int)(char *));

雖然我不會去實現這些函數,可是對於 void * 版本的 arg_parser,我能夠給出它的一個用法示例,即用於解析 zero 程序的 -m--mode 選項:

int *mode = arg_parser(argc, argv, "-m", "--mode", map_text_into_int);
printf("%d\n", *mode);
free(mode);

總結

C 能寫的程序,Guile 也能寫得出來,反之亦然。不要再說 C 能直接操做內存,操做硬件,而 Guile 不能……用 Guile 也能夠模擬出內存和硬件,而後再操做。大體的感受是,用 C 寫程序,會以爲本身在擺弄一臺小馬達,而用 Guile 寫程序,則以爲本身拿了根小數樹枝唆使一隻毛毛蟲。

Guile 語言最顯著的特色有兩個。第一個特色是,列表無處不在,甚至函數的定義、應用也都以列表的形式呈現的。第二個特色是,前綴表達式無處不在,正由於如此,咱們能夠在函數命名時可使用 =-? 之類的特殊符號。這兩個特色是其餘語言所不具有的,固然它也帶來重重的括號。說到括號,可能像 Guile 這些 Scheme 系的 Lisp 風格的語言,它們的括號嚇退了許多初學者。事實上,只要有個好一些的編輯器——我用的是 Emacs,而後動手寫一些代碼,很快就不怕了,甚至會感受它們很天然。

Guile 語言在語法上未提供循環,初次用遞歸來模擬迭代,會有些不直觀。多寫寫就習慣了。事實上,Guile 以宏的形式提供了功能強大的循環機制,對此之後再做介紹……其實如今我還不會用。在符合 Scheme 語言標準的前提下,Guile 也實現了一些屬於它本身的東西。本文中用到的 #nil 以及一些字符串運算函數,這都是 Scheme 語言標準以外的東西。

(cdr《爲本身寫本-Guile-書》)

相關文章
相關標籤/搜索