一:local function
Functions defined via defun or setf of symbol-function are global functions.
局部函数可以由labels来定义,他跟let类似,只是第一个参数不是用来指定新的变量而是一组函数的定义。该参数的形式如下:
( name parameters body)
(labels ((addlO (x) (+ x 10))
(consa (x) (cons 'a x)))
(consa (addlO 3)))
(A . 13)
与let不同的是,labels定义的函数可以访问labels中定义的任意函数,包括自身,所以说我们可以用它实现递归。
(do ((x a (b x))
(y c (d y)))
((test x y) (z x y))
(f x y))
Is equivalent to
CL-USER> (labels ((rec (x y)
(cond ((test x y)
(z x y))
(t
(f x y)
(rec (b x)(d y ))))))
(rec a c))
二:Feature of lisp programming 1:Lisp consists mostly of Lisp functions,you can modify the language to suit your ideas
2: the functional style seems even better adapted for writing reusable software,You can do the same thing in your own programs by writing utilities.
Mapint 找出n个符合函数fn的对象
Filter 从一个list中找出符合fn的对象
Most 从一个list中找出最符合某有一个条件的对象。
CL-USER> (defun map-int (fn n)
(let ((acc nil))
(dotimes (i n)
(push (funcall fn i) acc))
(nreverse acc)))
MAP-INT
CL-USER> (map-int #'identity 10)
(0 1 2 3 4 5 6 7 8 9)
CL-USER> (defun filter (fn lst)
(let ((acc nil))
(dolist (x lst)
(let ((val (funcall fn x)))
(if val (push val acc))))
(nreverse acc)))
FILTER
CL-USER> (map-int #'(lambda (x) (random 100)) 10)
(81 13 90 83 12 96 91 22 63 30)
CL-USER> (defun most (fn lst)
(if (null lst)
(values nil nil)
(let* ((wins (car lst))
(max (funcall fn wins)))
(dolist (obj (cdr lst))
(let ((score (funcall fn obj)))
(when (> score max)
(setf wins obj
max score))))
(values wins max))))
MOST
CL-USER> (most #'length '((a b) (a b c) (a)))
(A B C)
3
Typecase:which takes arguments of any type and combines them in a way appropriate to their type.
CL-USER> (defun combiner (x)
(typecase x
(number #'+)
(list #'append)
(t #'list)))
COMBINER
CL-USER> (defun combine (&rest args1)
(apply (combiner (car args1)) ;;从另一方面显示args1是一个列表形式。其实args 跟args1他们的值是不一样的。
args))
COMBINE
CL-USER> (combine 2 3)
5
CL-USER> (combine '(a b) '(c d))
(A B C D) ;; 根据car判断用哪个函数,然后
CL-USER> (defun combine (&rest args)
(funcall (combiner (car args))
args))
COMBINE
CL-USER> (combine '(a b) '(c d))
((A B) (C D))
apply 实现机理:
Apply 如果后面是多个列表的话,它会把这些列表调用list* 方法,然后对生成的列表调用values-list方法。注意values-list 他会返回prolist中的每个元素。而如果包含dot-list的话就会报错。所以这也就是为啥apply 最后一个参数必须是一个prolist。数字或者dotlist都是不行的。
为啥说最后一个参数必须是列表呢?因为list*只会对最后一个列表进行处理,并且一个列表中top-level不可能存在dot-list,比如((1 2).3 (4 5))肯定是不可能的,因为如果中间3是一个点列的话,就不会又后续了。如果次level为点对结构我就不管了,((1 2)(3.4)(5 6))只要这个dotlist不是在最后一个位置就行,((1 2)(3 4)(5 .6))因为它不像前面如果是dot-list的话,不管他,而现在如果你是dotlist,经过append以后。你的点就向外暴漏了一层。((1 2)(3 4)(5 .6))-->((1 2)(3 4)5 .6)这个时候values-list的话,就困惑了。
list* 如果单个列表的话,返回这个列表
如果多个列表就(append (n-1)-list nth-list)
如果是单个数字的话,返回这个数字
CL-USER> (list* '(1 2) '(4 6) (cons (cons 4 5) 7)) ((1 2) (4 6) (4 . 5) . 7)
CL-USER> (append (list '(1 2) '(4 6)) (cons (cons 4 5) 7)) ((1 2) (4 6) (4 . 5) . 7)
CL-USER> (list* 1) 1
CL-USER> (values-list 1) no value
Values-list 后面必须是一个列表,返回prolist中的元素(Prolist : must be a proper list.)
CL-USER> (values-list (list* '(1 3) '(9 7))) ;;凡是节点上的点对结构都是它的一个值。
(1 3)
9
7
CL-USER> (values-list '((1 3) (3 4) 6))
(1 3)
(3 4)
6
出错的例子:
CL-USER> (list* '(1 3) '(4 5) (cons 4 5))
((1 3) (4 5) 4 . 5)
CL-USER> (values-list (list* '(1 3) '(4 5) (cons 4 5)) )
总结:apply 后参数的形式:1:不能是单个的数字
2:如果是多列表的话,最后一个单元不能是数字或者dotlist
3:单个列表的话,最后一个单元不能是dotlist
证明apply 实现机理:
CL-USER> (apply (lambda (x y z)
(cons z (append x y )))
'(1 3) '(5 6))
(6 1 3 . 5)
问题1:也许你会想既然apply可以接受一个list,那么如果一个参数的形参也是&rest args,也就相当于我一个列表中不论多少个元素他都会接受。但是你注意了,如果你过多的给他参数的话,程序就会报错。
CL-USER> (apply (lambda (x y z)
(append x y z)) (list '(1 3) '(5 6) '(7 9)))
(1 3 5 6 7 9)
CL-USER> (apply (lambda (x y z)
(append x y z)) (list '(1 3) '(5 6) '(7 9) '(11 12 13)))
你应该同样会疑问为啥有个arg-0 = 4 吧。并且上面描述说 invalid number of argument 4. 也就是说参数的个数不符。然后我当时的怀疑是这个 4 的话,
1:只会在abend的时候才会赋值给arg-0 (比较可能)
2::本身args就会有一个args-0用于存储到底接受多少个参数。然后不匹配的话就报错。其实对于上面这两种情况我也想检测到底是哪个原因,唉……找不出来方法。。
3:或者这个args跟我们参数中的args仅仅是名字一样而已,不是同一个东西。
总结:Apply 不管三七二十一,它会把后面的先把后面的参数都经过 list* 生成一个 list再 说,然后如果新 list经过values-list处理过以后 ,参数太多的话,就会报错。并且注意最后一个 arg 不能是 dot-list 或数字。funcall 不管你给多少实参,我就是需要多少拿多少。
问题二:实参跟实际给函数时的形式不一样,即args != args1
因为我们前面已经说过,当你传递多个列表作为实参的时候,会先调用list*方法,然后调用values-list 会把新表中的每一个元素分好,
1:用于对应于函数的形参,比如上面的式子apply (lambda (x y z)
2:函数的形参也是&rest args1的话,他会把在传递给args1,把刚才values-list分好的块,然后组成一个新的列表,这个的好处首先是易于管理了,整个实参都在args1里面,并且比方说如果函数内部在有用apply方式调用args1的话,因为现在就是一个列表形式,它就不会再因为进行list*/values-list 而与传进来 时的值不一样了。
CL-USER> (apply (lambda (&rest args1)
(format t "~a ~%" args1)) '((2 3) (4 5)) '(8 9))
(((2 3) (4 5)) 8 9)
NIL
问题三:Mapcar/apply的区别
apply 后面的列表必须是量力而行。经过list* 和values-list处理过以后的实参数等于调用args的函数所需要的参数个数。并且其返回值的数量由调用函数来定。function所接受的参数类型,决定了经过函数调用以后,传给他的必须是这个类型。如本例它是oddp,所以args最后取values-list以后必须是只含一个atom的数字。也就是只能有一个元素传递给args1,这个也是为啥下面函数complement函数需要用mapcar的原因,它只能是一次传递一个否则会报错。
Mapcar function :function 需要几个参数,就跟几个列表。每一次调用function都是从各个列表中取出一个。
CL-USER> (mapcar (complement #'oddp)
'(1 2 3 4 5 6))
(NIL T NIL T NIL T)
CL-USER> (defun our-complement (f)
#'(lambda (&rest args)
(not (apply f args))))
OUR-COMPLEMENT
CL-USER> (mapcar (our-complement #'oddp) '(1 2 4 5 7 8))
(NIL T T NIL NIL T)
现在有个问题就是,你应该还记得下面这个式子,我当时感觉不伦什么,mapcar都是会传递一个值给函数。如果complement函数中args 接受了一个值1,那么到调用的时候肯定会出错。我们前面已经说了,apply后面的参数不能够是单个数字1.
CL-USER> (mapcar #'+ '(1 2 4 5 6)) (1 2 4 5 6) ;;+是一个二元函数
CL-USER> (mapcar #'+ '(1 2 4 5) '( 5 6 7)) (6 8 11)
原因是如果形参为&rest args,当
mapcar
后面的函数形参是
&rest
的时候,那么最后
args
的值将会将传过来的值封装起来到一个
list
中
,
而如果就是一个单纯的变量叫
args
,他的值就是传过来时的情形。其实
mapcar
还是仅仅只传一个数字的值,只是碰到
&rest
被封装了,其实我感觉把他封装起来的话,更好进行其它处理,因为
args
的值都在一个
list
中,你可以检验一下
&rest
后面
args
究竟是个什么东西。
CL-USER> (mapcar (lambda (&rest args)
(typecase args
(number (format t "~a is a number ~%" args))
(list (format t "~a is a list ~%" args)))) '(1 2 4))
(1) is a list
(2) is a list
(4) is a list
(NIL NIL NIL)
CL-USER> (mapcar (lambda (args)
(typecase args
(number (format t "~a is a number ~%" args))
(list (format t "~a is a list ~%" args)))) '(1 2 4))
1 is a number
2 is a number
4 is a number
(NIL NIL NIL)
对上面complement的分析:
首先(our-complement #'oddp) 它会返回一个lambda函数,表面上看着它是接受args个参数,既然接受一个列表,能够用mapcar 也就同样能够用apply了。但是关键的地方是里面有一个(apply f args)这个args它接受的参数还需要f来定。如本例oddp假设你将mapcar换为了apply,现在args='(1 2 4 5 7 8).然后(apply #'oddp '(1 2 4 5 7 8)),而oddp只能是接受一个参数,而现在你一下子给了他6个值,所以会报错。
CL-USER> (apply (our-complement #'+ ) '(1 2 3 5 6)) ;;对比上面mapcar是function的区别
NIL
对+的说明
因为#‘+是一个多元函数,其实下面几种形式都是等价的,初看的话funcall中的形式比较直观,因为给的就是值,而apply通过list*和values-list转化以后同样是孤零零的值传给形参args,然后args会把这些值在重新放到一个列表里面。所以我感觉至于#’+内部肯定是用apply来调用args.而不是funcall.哈哈,如果还
用apply调用的话,你会发现它自己是个环。所以那部分是臆想。
CL-USER> (apply #'+ '( 1 3 4 5))
13
CL-USER> (funcall #'+ 1 3)
4
CL-USER> (funcall #'+ 1 3 4 5 5)
18