老古董Lisp实用主义入门教程(6):好奇先生在Lisp的花园里挖呀挖呀挖

logo

好奇先生Lisp探索工具explore-lisp

好奇先生学会了懒惰先生交给他的Package开发和管理流程,quickproject管理package。好奇先生一步一步定义了一个叫做explore-lisp的package,这个包提供了探索Lisp的工具,并且提供了把探索结果输出成markdown文档的方法。

这个工程的地址在explore-lisp。当把这个源代码下载(clone)到本地之后,可以通过quicklispquickload函数加载这个包。

在安装之前,记得要把文件克隆到quicklisp的本地目录下,比如~/quicklisp/local-projects

或者还有一个办法,克隆到任何地方,除了利用修改变量asdf:*central-registry*方式,还可以直接在quicklisplocal-projects目录下建立一个软链接,指向这个包的目录。

mklink /D ~/quicklisp/local-projects/explore-lisp  /path/to/explore-lisp 

然后在REPL中加载这个包:

(ql:quickload :explore-lisp)

这之后,就可以用这个包的功能了。

* (el:dir 'el)
(EXPLORE-LISP:FORMAT-DESCRIPTIONS EXPLORE-LISP:DESCRIBE-SYMBOL EXPLORE-LISP:DIR
                                  EXPLORE-LISP:EXPORT-ALL-EXTERNAL-SYMBOLS
                                  EXPLORE-LISP:EXPORT-DESCRIPTIONS)
5

这个包本身的dir功能就能够列出这个包的所有函数和变量。还可以用

(el:describe-symbol 'el:dir)
"EXPLORE-LISP:DIR
  [symbol]

DIR names a compiled function:
  Lambda-list: (PACKAGE)
  Derived type: (FUNCTION (T) (VALUES LIST UNSIGNED-BYTE &OPTIONAL))
  Documentation:
    List all external symbols in a package, return a list of symbol names and its length
  Source file: C:/Users/qchen/quicklisp/local-projects/explore-lisp/explore-lisp.lisp
"

可以看到这个符号的文档有几个基本的部分:

  • 函数的全名(包括包的名字作为前缀)
  • 符号的类型(这里是一个symbol
  • 下面就是函数的定义,包括参数列表和返回值的类型
  • 文档字符串,这个字符串描述了这个函数的功能
  • 函数的源代码文件

好奇先生运行了(el:export-all-external-symbols 'el :fn "docs.md" :start-level 2),这个函数的功能是把el包的所有外部符号都导出来,哇啦,一个文件docs.md就生成了,里面包含了这个包的所有外部符号的文档。

挖一下explore-lisp

下面的文档主体部分,都是通过好奇先生的探索工具集生成的。真的是太好玩,好奇先生可以玩一整天。

  1. DESCRIBE-SYMBOL
  2. DIR
  3. EXPORT-ALL-EXTERNAL-SYMBOLS
  4. EXPORT-DESCRIPTIONS
  5. FORMAT-DESCRIPTIONS
  6. SEARCH-SYMBOLS

DESCRIBE-SYMBOL

这个函数可以把一个符号的文档描述输出成一个字符串。可以看到这个函数的签名,以及参数列表,返回值的类型,文档描述,函数的源代码文件位置。

EXPLORE-LISP:DESCRIBE-SYMBOL
  [symbol]

DESCRIBE-SYMBOL names a compiled function:
  Lambda-list: (NAME)
  Derived type: (FUNCTION (T) (VALUES SIMPLE-STRING &OPTIONAL))
  Documentation:
    Describe a symbol and return the output as a string
  Source file: C:/Users/qchen/quicklisp/local-projects/explore-lisp/explore-lisp.lisp

DIR

这个函数列出一个包的所有外部符号,返回一个符号名字的列表和列表的长度。

EXPLORE-LISP:DIR
  [symbol]

DIR names a compiled function:
  Lambda-list: (PACKAGE)
  Derived type: (FUNCTION (T) (VALUES LIST UNSIGNED-BYTE &OPTIONAL))
  Documentation:
    List all external symbols in a package, return a list of symbol names and its length
  Source file: C:/Users/qchen/quicklisp/local-projects/explore-lisp/explore-lisp.lisp

EXPORT-ALL-EXTERNAL-SYMBOLS

这个函数把一个包的所有外部文件列出在一个文件中,可以通过关键词来设定文件名称和最开始文档在markdown中的标题级别。

这里最有趣的是这个,这个函数的源程序直接被列在这里?为什么?为什么其他的函数没有呢?只是给出了函数对应文件的位置。这就是Lisp最为独特的地方,Lisp的运行环境(REPL)中的符号都是动态的,可以被修改,可以被重新定义,可以被删除。这个函数的源代码就是这样被列在这里的。

这里有一个Lisp的核心概念,一个运行的环境中所有的符号、绑定都可以很容易的存成一个文件,然后在另一个环境中重新加载,这就是Lisp的强大之处。

所以很多资深Lisp程序员都说,Lisp程序是一个不停进化的活的程序,运行时修改功能,增加功能,删除功能,都是很容易的事情。

EXPLORE-LISP:EXPORT-ALL-EXTERNAL-SYMBOLS
  [symbol]

EXPORT-ALL-EXTERNAL-SYMBOLS names a compiled function:
  Lambda-list: (PACKAGE &KEY (FN  FN-P) (START-LEVEL 1))
  Derived type: (FUNCTION (T &KEY (:FN T) (:START-LEVEL T))
                 (VALUES NULL &OPTIONAL))
  Documentation:
    List all external symbols in a package and their doc strings into a file ~package~.md
  Source form:
    (LAMBDA
        (PACKAGE
         &KEY (EXPLORE-LISP::FN "" EXPLORE-LISP::FN-P)
         (EXPLORE-LISP::START-LEVEL 1))
      "List all external symbols in a package and their doc strings into a file ~package~.md"
      (BLOCK EXPLORE-LISP:EXPORT-ALL-EXTERNAL-SYMBOLS
        (LET ((EXPLORE-LISP::SORTED-NAMES
               (EXPLORE-LISP::SORT-SYMBOLS (EXPLORE-LISP:DIR PACKAGE)))
              (EXPLORE-LISP::_FN
               (IF EXPLORE-LISP::FN-P
                   EXPLORE-LISP::FN
                   (FORMAT NIL "~a.md" PACKAGE))))
          (WITH-OPEN-STREAM
              (EXPLORE-LISP::S
               (OPEN EXPLORE-LISP::_FN :DIRECTION :OUTPUT :IF-EXISTS
                     :SUPERSEDE))
            (FORMAT EXPLORE-LISP::S "~A ~A external symbols~%~%~%"
                    (EXPLORE-LISP::MARKDOWN-NTH-HEADER
                     EXPLORE-LISP::START-LEVEL)
                    PACKAGE)
            (LET ((EXPLORE-LISP::INDEX 1))
              (DOLIST (EXPLORE-LISP::NAME EXPLORE-LISP::SORTED-NAMES)
                (FORMAT EXPLORE-LISP::S "~d. [~A](#~A)~%"
                        EXPLORE-LISP::INDEX EXPLORE-LISP::NAME
                        EXPLORE-LISP::NAME)
                (INCF EXPLORE-LISP::INDEX)))
            (FORMAT EXPLORE-LISP::S "~%~%")
            (DOLIST (EXPLORE-LISP::NAME EXPLORE-LISP::SORTED-NAMES)
              (FORMAT EXPLORE-LISP::S "~A  `~A`~%~%"
                      (EXPLORE-LISP::MARKDOWN-NTH-HEADER
                       (+ 1 EXPLORE-LISP::START-LEVEL))
                      EXPLORE-LISP::NAME)
              (FORMAT EXPLORE-LISP::S "```lisp~%")
              (DESCRIBE EXPLORE-LISP::NAME EXPLORE-LISP::S)
              (FORMAT EXPLORE-LISP::S "```~%"))))))

EXPORT-DESCRIPTIONS

将一个名称列表的描述导出到一个文件中,可以通过关键词来设定文件名称和最开始文档在markdown中的标题级别。

EXPLORE-LISP:EXPORT-DESCRIPTIONS
  [symbol]

EXPORT-DESCRIPTIONS names a compiled function:
  Lambda-list: (NAME-LIST FN &OPTIONAL (START-LEVEL 1))
  Derived type: (FUNCTION (T T &OPTIONAL T) (VALUES NULL &OPTIONAL))
  Documentation:
    Save a list of symbol names to a file
  Source file: C:/Users/qchen/quicklisp/local-projects/explore-lisp/explore-lisp.lisp

FORMAT-DESCRIPTIONS

将一个名称列表的描述格式化成markdown文档,可以通过关键词来设定文件名称和最开始文档在markdown中的标题级别。

EXPLORE-LISP:FORMAT-DESCRIPTIONS
  [symbol]

FORMAT-DESCRIPTIONS names a compiled function:
  Lambda-list: (NAME-LIST &OPTIONAL (START-LEVEL 1))
  Derived type: (FUNCTION (T &OPTIONAL T)
                 (VALUES SIMPLE-STRING &OPTIONAL))
  Documentation:
    Format a list of symbol names as markdown, with optional start level for headers
  Source file: C:/Users/qchen/quicklisp/local-projects/explore-lisp/explore-lisp.lisp

SEARCH-SYMBOLS

这个函数可以搜索一个包中的所有符号,包括符号的名字和文档字符串。文档字符串内的搜索是可选的。所有的搜索和字符串比较都是不区分大小写的。

EXPLORE-LISP:SEARCH-SYMBOLS
  [symbol]

SEARCH-SYMBOLS names a compiled function:
  Lambda-list: (NAME PACKAGE &KEY (DOC-STRING NIL))
  Derived type: (FUNCTION (T T &KEY (:DOC-STRING T))
                 (VALUES LIST &OPTIONAL))
  Documentation:
    Search for string in symbol names and doc strings in a package
  Source file: C:/Users/qchen/quicklisp/local-projects/explore-lisp/explore-lisp.lisp

挖一下string

好奇先生探索工具非常简单,每个函数功能都很单一。并且,在(ql:quickload 'explore-lisp)之后,如果对功能不满意或者发现运行不符合预期,

  • 可以在源代码所在位置(describe一下任意一个函数就知道)修改,然后重新加载,(require 'explore-lisp)
  • 直接在VSCode中打开一个任意新文件,(in-package :explore-lisp),编写函数新定义,然后运行一下,也能达到效果。

这就是Lisp的动态开发理念,与传统的“编译-运行-调试”不同,Lisp把当前REPL中的所有符号都当作一个整体,可以随时修改,随时调试,随时运行。并且,Lisp的函数绑定都是动态的,这就鼓励各种运行时修改的开发模式。

另外,好奇先生觉得最后这个函数实在是太好玩了……他太好奇了。

好奇先生应该睡觉,但是他就是忍不住,又在REPL中敲一个命令:

(el:search-symbols "string" :common-lisp)

好奇先生还没来得及按下回车,挠痒痒先生的手从很远的地方伸过来,挠了一下好奇先生的咯吱窝,然后还按了一下键盘,好奇先生的命令就被执行了。

啊,Lisp的REPL关于字符串的函数全部跑出来了~~~一共有46个!

(BASE-STRING DIRECTORY-NAMESTRING ENOUGH-NAMESTRING FILE-NAMESTRING
 FILE-STRING-LENGTH GET-OUTPUT-STREAM-STRING HOST-NAMESTRING MAKE-STRING
 MAKE-STRING-INPUT-STREAM MAKE-STRING-OUTPUT-STREAM NAMESTRING
 NSTRING-CAPITALIZE NSTRING-DOWNCASE NSTRING-UPCASE PARSE-NAMESTRING
 PRIN1-TO-STRING PRINC-TO-STRING READ-FROM-STRING SIMPLE-BASE-STRING
 SIMPLE-STRING SIMPLE-STRING-P STRING STRING-CAPITALIZE STRING-DOWNCASE
 STRING-EQUAL STRING-GREATERP STRING-LEFT-TRIM STRING-LESSP STRING-NOT-EQUAL
 STRING-NOT-GREATERP STRING-NOT-LESSP STRING-RIGHT-TRIM STRING-STREAM
 STRING-TRIM STRING-UPCASE STRING/= STRING< STRING<= STRING= STRING> STRING>=
 STRINGP WITH-INPUT-FROM-STRING WITH-OUTPUT-TO-STRING WRITE-STRING
 WRITE-TO-STRING)

来,对子一个base-string继续哇呀挖:

(el:describe-symbol 'base-string)
COMMON-LISP:BASE-STRING
  [symbol]

BASE-STRING names the built-in-class #<BUILT-IN-CLASS COMMON-LISP:BASE-STRING>:
  Class precedence-list: BASE-STRING, STRING, VECTOR, ARRAY, SEQUENCE, T
  Direct superclasses: STRING
  Direct subclasses: SIMPLE-BASE-STRING
  Sealed.
  No direct slots.

BASE-STRING names a primitive type-specifier:
  Lambda-list: (&OPTIONAL SIZE)
  

好奇先生的好奇心彻底爆炸,这里居然有很不一样的东西,base-string是一个内置的类,继承自string,并且有一个直接的子类simple-base-string。它的祖先依次是stringvectorarraysequencet。这又是什么意思?而且这里的sequence是什么?

所有的类都是t这很好理解,因为除了nil,其他所有东西都是t,在面向对象的编程中,is也就是的关系就是继承关系。那么sequence是什么呢?

好奇先生把这里的Class precedence-list, Direct superclasses, Direct subclasses都挖了出来,然后一路挖下去……

挖到了sequence

最后,好奇先生挖出来一个下面这样的继承关系图:

classDiagram
    T <|-- SYMBOL
    T <|-- SEQUENCE
    T <|-- ARRAY
    SIMPLE-ARRAY <|-- SIMPLE-VECTOR
    SEQUENCE <|-- LIST
    SEQUENCE <|-- VECTOR
    ARRAY <|-- VECTOR
    VECTOR <|-- STRING
    VECTOR <|-- SIMPLE-VECTOR
    STRING <|-- BASE-STRING
    STRING <|-- SIMPLE-STRING
    SIMPLE-ARRAY <|-- SIMPLE-STRING
    ARRAY <|-- SIMPLE-ARRAY
    SYMBOL <|-- NULL
    LIST <|-- NULL
    LIST <|-- CONS
    SIMPLE-ARRAY <|-- SIMPLE-BIT-VECTOR
    BASE-STRING <|-- SIMPLE-BASE-STRING
    SIMPLE-STRING <|-- SIMPLE-BASE-STRING

在这里插入图片描述

啊哈,好奇先生觉得好玩极了!从这个图里可以看到,sequence是一个很重要的类,它是listvector的祖先,所有的跟字符串相关的类都是sequence的子类。

那么还能怎么挖呢?当然是看看哪些操作(函数)直接定义在sequence上了。

(sort (el:search-symbols "sequence" :common-lisp :doc-string t) #'string-lessp)
(* *FEATURES* BASE-STRING BIT-VECTOR CONCATENATE CONS COPY-SEQ COUNT COUNT-IF
   COUNT-IF-NOT DELETE DELETE-DUPLICATES DELETE-IF DELETE-IF-NOT ELT EVERY FILL
   FIND FIND-IF FIND-IF-NOT LENGTH LIST MAKE-SEQUENCE MAP MAP-INTO MAPHASH
   MERGE MISMATCH NOTANY NOTEVERY NREVERSE NSUBSTITUTE NSUBSTITUTE-IF
   NSUBSTITUTE-IF-NOT NULL POSITION POSITION-IF POSITION-IF-NOT PROGRAM-ERROR
   READ-SEQUENCE REDUCE REMOVE REMOVE-DUPLICATES REMOVE-IF REMOVE-IF-NOT
   REPLACE REVERSE SEARCH SEQUENCE SIMPLE-BASE-STRING SIMPLE-BIT-VECTOR
   SIMPLE-CONDITION SIMPLE-STRING SIMPLE-VECTOR SOME SORT STABLE-SORT STRING
   STRING-LEFT-TRIM STRING-RIGHT-TRIM STRING-TRIM SUBSEQ SUBSTITUTE
   SUBSTITUTE-IF SUBSTITUTE-IF-NOT T THE TYPE-ERROR VECTOR
   WITH-HASH-TABLE-ITERATOR WRITE-SEQUENCE)

真是完美,好奇先生的好奇心又双叒叕被点燃了。这里有很多操作,比如concatenatecopy-seqcountcount-ifcount-if-notdeletedelete-duplicatesdelete-ifdelete-if-notelteveryfillfindfind-iffind-if-notlengthlistmake-sequencemapmap-intomaphashmergemismatchnotanynoteverynreversensubstitutensubstitute-ifnsubstitute-if-notnullpositionposition-ifposition-if-notread-sequencereduceremoveremove-duplicatesremove-ifremove-if-notreplacereversesearchsequencesortstable-sortstringstring-left-trimstring-right-trimstring-trimsubseqsubstitutesubstitute-ifsubstitute-if-notthetype-errorvectorwith-hash-table-iteratorwrite-sequence

好奇先生是没法睡觉了……

结论

  1. Lisp自己学自己简直不要太开心
  2. Lisp的开发心态就是演进,利用动态绑定,随时更改函数的行为模式
  3. Lisp中的对象继承关系也能通过describe来探索
  4. Lisp会自己给自己写文档(Lisp实际上就是这么做的)
  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大福是小强

除非你钱多烧得慌……

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值