CL21 是对 Comong Lisp 重新设计的实验性项目。它让一些单调乏味的东西变得更加容易,同时带来了一些流行的新特性:
- 更强大的函数是编程,
- 更多的面向对象以及泛型函数
- 新的语法(正则表达式、哈希表、字符串插入等)
- 几个由符号组成的包
- 完全由 Common Lisp 编写
CL21 具有一定的破坏行,因为它重定义了常用的符号(尤其是泛型函数)。尽管如此,在不确定的时候,可以通过调用 _cl:_
包来正常的使用常规的 CL 符号。你可能想看一些相关的项目,这些项目超越了 Alexandria 并在 CL 的世界中一直保留着 “良民” 的身份:
- rutils -
a comprehensive and all-encompassing suite of syntactic utilities to
support modern day-to-day Common Lisp development. It adds readtable
literal syntax for shorter lambdas, hash-tables and vectors
(…``#
,#h
and#{…}
,#v
), some generic functions
(whose name start withgeneric-
),
shortcuts
for standard operators, and many helpful functions. - Serapeum is a set of utilities
beyond Alexandria, as a supplement. It defines a lot of helper
functions (see its
reference)
for macros, data structures (including more functions for trees,
queues), sequences (partition
,keep
,…), strings, definitions
(defalias
,…),… no new reader macros here.
动机
摘抄自 http://cl21.org/ :
Dear Common Lispers,
Common Lisp has the most expressive power of any modern language. It has first class functions with lexical closures, an object system with multiple-dispatch and a metaobject protocol, true macros, and more. It is ANSI standardized and has numerous high-performance implementations, many of which are free software.
In spite of this, it has not had much success (at least in Japan). Its community is very small compared to languages like Ruby and most young Lispers are hacking with Clojure.
Why? Common Lisp is much faster than them. Ruby has no macros and even Clojure doesn’t have reader macros. Why then?
Because these languages are well-designed and work for most people for most purposes. These languages are easy to use and the speed isn’t an issue.
Is Common Lisp sufficiently well-designed? I don’t think so. You use different functions to do the same thing to different data types (elt, aref, nth). You have long names for commonly used macros (destructuring-bind, multiple-value-bind). There is no consistency in argument order (getf and gethash). To put it simply, the language is time-consuming to learn.
Given this, how can programmers coming from other languages believe Common Lisp is the most expressive one?
Fortunately in Common Lisp we can improve the interface with abstractions such as functions, macros, and reader macros. If you believe our language is the most expressive and powerful language, then let’s justify that belief.
We should consider the future of the language, not only for ourselves but for the next generation.
安装和使用
CL21 在 Quicklisp 中也有.
获取最新的版本:
(ql-dist:install-dist "http://dists.cl21.org/cl21.txt")
(ql:quickload :cl21)
使用方法:
(in-package :cl21-user)
(defpackage myapp (:use :cl21))
(in-package :myapp)
特性
请记住,以下只是 CL21 特性的摘要。为了确保没有遗漏任何东西,应该去阅读 CL21 的维基,里面自动列出了 CL21 对标准 CL 的主要修改。更好的一点是,维基上面的是源代码
也就是说,在新的项目中使用 CL21 是很值得的!
函数式编程
简短的 lambda
lm
是个创建匿名函数的宏:
(lm (x) (typep x 'cons))
;=> #<FUNCTION (LAMBDA (X)) {1008F50DAB}>
(map (lm (x) (+ 2 x)) '(1 2 3))
;=> (3 4 5)
^
是 lm
的简写。
^(typep % 'cons)
;=> #<FUNCTION (LAMBDA (%1 &REST #:G1156 &AUX ...)) {10092490CB}>
(map ^(+ 2 %) '(1 2 3))
;=> (3 4 5)
不使用的参数会被自动忽略。
(map ^(random 10) (iota 10))
;=> (6 9 5 1 3 5 4 0 7 4)
%n
表示第 n 个参数(从 1 开始)。%
等价于 %1
。
(sort '(6 9 5 1 3 5 4 0 7 4) ^(< %1 %2))
;=> (0 1 3 4 4 5 5 6 7 9)
function
和 compose
function
是个特殊的操作副,用来获取给定表单中的函数值。
当给定一个符号时, function
会返回该符号的函数值。
(function integerp)
;=> #<FUNCTION INTEGERP>
如果给定的表单是以 compose
、and
、or
或 not
开头,function
返回的是一个复合函数:
(function (compose - *))
<=> (compose (function -) (function *))
(function (and integerp evenp))
<=> (conjoin (function integerp) (function evenp))
(function (or oddp zerop))
<=> (disjoin (function oddp) (function zerop))
(function (not zerop))
<=> (complement (function zerop))
#'
是 function
的简写。
#'(compose - *)
#'(and integerp evenp)
#'(or oddp zerop)
#'(not zerop)
#'(and integerp (or oddp zerop))
Currying
CL21 添加了新的符号:curry
和 rcurry
。参见第1章:函数。
惰性序列
(use-package :cl21.lazy)
(defun fib-seq ()
(labels ((rec (a b)
(lazy-sequence (cons a (rec b (+ a b))))))
(rec 0 1)))
(take 20 (fib-seq))
;=> (0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181)
(take 3 (drop-while (lambda (x) (< x 500)) (fib-seq)))
;=> (610 987 1597)
不可变的数据结构
实际上 CL21 并不包括这部分,但值得在本章节中添加进去。对于不可变的数据结构,参见 Fset 库(收录在 Quicklisp 中)。
泛型函数
以下是与 CL 的普通函数同名的泛型函数
- getf
- equalp
- emptyp
- coerce
(defvar *hash* #H(:name "Eitaro Fukamachi" :living "Japan"))
(getf *hash* :name)
;=> "Eitaro Fukamachi"
(coerce *hash* 'plist)
;=> (:LIVING "Japan" :NAME "Eitaro Fukamachi")
你可以在自定义的类中定义上面这些方法。
以下是新的函数:
append
flatten
elt
: Returns the element at position INDEX of SEQUENCE or signals
a ABSTRACT-METHOD-UNIMPLMENETED error if the sequence method is not
implemented for the class of SEQUENCE.emptyp
equalp
split
,split-if
drop
,drop-while
take
,take-while
join
length
keep
,keep-if
,nkeep
,nkeep-if
partition
,partition-if
映射
在 Common Lisp 中,以 “map” 开头的函数是高阶函数,其参数是一个函数和一个序列。
在 CL21 中也一样,但在 CL21 中,“map” 函数会返回一个值。这是为了和 “迭代”、“映射” 中的 “map” 区分开。
基于上面的原因,CL21 不再支持 CL 的 mapc
和 mapl
。maphash
是支持的,但 maphash
会返回一个新的哈希表。
(maphash (lm (k v)
(cons k (1+ v)))
#H(:a 1 :b 2))
;=> #H(:B 3 :A 2)
map
和 Common Lisp 中的 mapcar
一样,除此之外 map
接受任意的序列,不只是列表。
(map #'- '(1 2 3 4))
;=> (-1 -2 -3 -4)
(map #'- #(1 2 3 4))
;=> #(-1 -2 -3 -4)
CL21 不支持 mapcar
,可以使用 map
。
过滤的话使用的是 keep
、keep-if
(而不是 remove-if[-not]
)以及 nkeep[-if]
。
迭代
Common Lisp 有简单的迭代措施:dolist
、dotimes
和 dolist
。
除此之外,CL21 还增加了一个:doeach
。
doeach
和 dolist
一样,不过 doeach
可以对任意序列和哈希表进行操作。
(doeach (x '("al" "bob" "joe"))
(when (> (length x) 2)
(princ #"${x}\n")))
;-> bob
; joe
(doeach ((key value) #H('a 2 'b 3))
(when (> value 2)
(print key)))
;=> B
析构绑定形式可以放在变量的位置
(doeach ((x y) '((1 2) (2 3) (3 4)))
(print (+ x y)))
;-> 3
; 5
; 7
CL21 当然也有 while
关键词。
新数据类型
添加了以下的数据类型:
- proper-list
- plist
- alist
- octet
- file-associated-stream
- character-designator
- function-designator
- file-position-designator
- list-designator
- package-designator
- stream-designator
- string-designator
大部分新的数据类型都是从 trivial-types 中引入的。
字符串
双引号字符表示的就是字符串的宏字符。
"Hello, World!"
;=> "Hello, World!"
反斜杠后的字符是转译符,和 C 语言中的转译符一致。
"Hello\nWorld!"
;=> "Hello
; World!"
字符串插入
#"
和 "
一样, 但是 #"
可以在字符串中插入值。
当遇到 ${...}
或 @{...}
时, #"
中的会将表达式(或是最后一个表达式)运算求值,然后用得到的结果将原表达式替换掉。
#"1 + 1 = ${(+ 1 1)}"
;=> "1 + 1 = 2"
哈希表
CL21 给哈希表提供了标志符号。
#H(:name "Eitaro Fukamachi" :living "Japan")
;=> #H(:LIVING "Japan" :NAME "Eitaro Fukamachi")
注意上面这种方法创建的哈希表的测试函数是 EQUAL
。如果你想使用其他的测试函数来创建,可以使用函数 hash-table
。
(hash-table 'eq :name "Eitaro Fukamachi")
;=> #H(:NAME "Eitaro Fukamachi")
; instead of
; (defvar *hash* (make-hash-table))
; (setf (gethash :name *hash*) "Eitaro Fukamachi")
也可以通过 getf
(而不是 gethash
)来访问元素。
(getf *hash* :name)
循环遍历哈希表:
(doeach ((key val) *hash*)
(when (< (length key) 2)
(princ #"${x}\n")))
将哈希表转换为 plist:
(coerce *hash* 'plist)
向量
向量的标志是 #(...)
。与 Common Lisp 中不同的是,新的向量中的元素会被计算求值而且创建的向量大小也是自适应的。
(let ((a 1)
(b 2)
(c 3))
#(a b c))
;=> #(1 2 3)
(defvar *vec* #(0))
(push 1 *vec*)
;=> #(1 0)
(push-back 3 *vec*)
;=> #(1 0 3)
(pop *vec*)
;=> 1
正则表达式
新的正则表达式读取宏使用的是新的“语法” 插件。
(use-package :cl21.re)
(#/^(\d{4})-(\d{2})-(\d{2})$/ "2014-01-23")
(re-replace #/a/ig "Eitaro Fukamachi" "α")
运行外部程序 (cl21.process)
使用 run-process
:
(use-package :cl21.process)
(run-process '("ls" "-l" "/Users"))
;-> total 0
; drwxrwxrwt 5 root wheel 170 Nov 1 18:00 Shared
; drwxr-xr-x+ 174 nitro_idiot staff 5916 Mar 5 21:41 nitro_idiot
;=> #<PROCESS /bin/sh -c ls -l /Users (76468) EXITED 0>
或是 `#`` 宏:
#`ls -l /Users`
;=> "total 0
; drwxrwxrwt 5 root wheel 170 Nov 1 18:00 Shared
; drwxr-xr-x+ 174 nitro_idiot staff 5916 Mar 5 21:41 nitro_idiot
; "
; ""
; 0
常量命名
所有的常量都被重命名,在原来名称的前后加上 “+” 号:
+pi+
;=> 3.141592653589793d0
+array-rank-limit+
;=> 65529
CL21 标准库
CL21 标准库是由一系列使用 CL21 发布的库,其目的是提供各种各样的设施。
我们正在努力增加标准库的数量,目前有以下的软件包/库:
- cl21.re
- cl21.process
- cl21.os
- cl21.lazy
- cl21.abbr