lisp 函数(一)

来源:互联网 发布:天刀神威捏脸数据男 编辑:程序博客网 时间:2024/05/16 01:47

一:定义新函数

1:(defun name (parameter*)

"optional documentation string"

Body-form*)

Name: 可以是任何包含字典字符和连字符(不是下划线)

Remark:一个字符串紧跟在形参列表之后,它应该是描述函数用途的文档字符串,定义时被关联到函数名,以后可用DOCUMENTATION来调用。

Body:可以由任何数量lisp表达式构成,最后一个表达式的值将作为整个函数的值。

2:The predicate fboundp tells whether there is a function with a given symbol as its name. If a symbol is the name of a function, symbol-function will return it,By setting the symbol-function of some name to a function, then we can use it.

CL-USER> (fboundp '+)TCL-USER> (symbol-function '+)#<FUNCTION +>CL-USER> (setf (symbol-function 'add2)        (lambda (x) (+ x 2)))#<FUNCTION (LAMBDA (X)) {252FA2E5}>

二:函数列表形参

形参基本用途就是为了声明一些变量,接受实参;针对必要形参而言,函数以过少或过多的实参来调用的话,lisp都会报错。

为了灵活性的实现,除了必要形参以外,还提供了一下三种 有可选形参(&optional),单一形参绑定到含有任意多个额外参数的列表上(&rest),通过关键字而不是位置映射实参(&key)

2.1 可选形参

实参已经用完,剩余的可选形参自动绑定到NIL

CL-USER> (defun foo (a b &optional c d)   (list a b c d))FOOCL-USER> (foo 1 2)     (1 2 NIL NIL)CL-USER> (foo 1 2 3)   (1 2 3 NIL)CL-USER> (foo 1 2 3 4) (1 2 3 4)设置默认值CL-USER> (defun foo (a &optional ( b 10)) (list a b))STYLE-WARNING: redefining COMMON-LISP-USER::FOO in DEFUNFOOCL-USER> (foo 1 2)     (1 2)CL-USER> (foo 1)       (1 10)

基于其他形参来计算默认值(有点类似let* 宏),如下是返回矩阵的函数,但是想让他特别方便的返回正方形。

CL-USER> (defun make-rectangle (width &optional (height width)))

对于函数默认值的参数,你肯定有需要知道这个值是默认值还是被调用者特别指明的,只要是针对当两个值相等的情况。你可以通过在表达式的默认值后面添加另一个变量P(形参名加-supplied-p后缀),如果当前的形参被指定了一个形参,这个变量P就为真,否则为NIL

你可以在函数中直接调用这个变量P

CL-USER> (defun foo (a b &optional (c 3 c-supplied-p))   (list a b c c-supplied-p))STYLE-WARNING: redefining COMMON-LISP-USER::FOO in DEFUNFOOCL-USER> (foo 1 2)     (1 2 3 NIL)CL-USER> (foo 1 2 3)   (1 2 3 T)CL-USER> (foo 1 2 4)   (1 2 4 T)

2.2 剩余形参

形参还有一种情况就是,你根本就不知道他会有多少个形参,这个时候用可选形参就不行了,因为可选形参里你提供的是最多那么多形参的格式,用下面的表达式表述更贴切。比如既可以接受一个形参,又可以两个甚至更多,这个时候我们就用&rest来体现。

(+)

(+ 1)

(+ 1 2)

&rest可以使一揽子形参,如果函数带&rest形参,那么任何满足了必要和可选形参之后的剩余的实参都将被收集到&rest形参的值,也从片面说了只能是&optional后跟&rest,而不可能是&rest后面跟了一个&optional.

+的形参列表看起来应该如下(可以接受任意多个形参)
(defun + (&rest numbers))

2.3关键字形参

可选形参和剩余形参虽然带来了很大灵活性,但是他么有个问题就是他们传递实参时是按位置匹配的,现在假设我只要定义第3个形参,那么前面两个就成为了必要形参,我必须将他们在实参里面指定。&key就是为了解决这个问题。

(defun foo (&key a b c)(list a b c))

当你调用函数的时候,每个关键字形参都被绑定到同名键后面的那个值,(foo :a 1 :b 2 :c 3),关键字都是以冒号开始的,并且自动定义成自求值变量,也就是值是他本身。既然都是以冒号开始为啥函数定义的时候前都没有冒号,只有当他是一个变量的时候他可以任意接受对象的值,而&key只是说明他要接受的是关键字实参。

注意点:b-supplied-p判断是实参是否为它指定了值,只有当默认值跟指定值一样时才会出现NIL的情况;默认形参可以引用早先出现的形参如C

CL-USER> (defun foo (&key (a 0)(b 0 b-supplied-p)(c (+ a b)))   (list a b c b-supplied-p))STYLE-WARNING: redefining COMMON-LISP-USER::FOO in DEFUNFOOCL-USER> (foo :a 1)             (1 0 1 NIL)CL-USER> (foo :b 1)             (0 1 1 T)CL-USER> (foo :b 1 :c 4)        (0 1 4 T)CL-USER> (foo :a 1 :b 1 :c 4)   (1 1 4 T)

如果出于某种原因想让调用者用来指定的形参的关键字不同于实参名。也就是为你的形参起个别名。具体操作就是将形参名替换成一个列表如 a --> (:apple a)其中包含调用函数式关键字的名字以及用作形参的名字。

现在说一下什么时候用关键字,关键字是一个自求值,也就是他的值就是它本身,你不能通过setf来对其进行更改,如下图中所示,并且对于lisp你得总有一个概念叫求值,无论对什么对象都得对他求值,比如上面的例子中形参用了一个变量a,这样我们就可以对他进行赋值了,你再想想还有没有其他可以接受对象的一个参数('a , :a , "a")因为这些都是自求值了,所以不能够最为形参去接受实参对象,然后在函数定义的地方,凡是遇到a的话都会求其值为你实参给的对象,关键字形参是接受一个plist对象(关键字/值),正好应了lisp处处求值的现象。下面同样给出了当定义中你不用形参a而用关键字:apple :box :cat,求值得到最后的显示是一个符号的列表。

同样对于这个别名为啥用结构(:apple a)而不是("apple" a)/('apple a)/(apple a),其中"apple" 'apple都是同样的原因,因为他们不具有:apple的特性,既是自求值并且可以关联一个valueplist结构,因为如果是string或引用的话,你怎么让那些形参对应那些值呀?然后就说(apple a)如果调用后的形式为(foo apple 10 box 20 cat 30),他会求apple的值,当时是一个unbound的,就会报错。综上必须用:apple才是最佳的方式。

对于关键字/值的plist结构,待后面章节描述。

CL-USER> (defun foo (&key ((:apple a)) ((:box b) 0) ((:cat c) 0 c-supplied-p))   (list a b c c-supplied-p))FOOCL-USER> (foo :a 10 :b 20 :c 30); No valueCL-USER> (foo :apple 10 :box 20 :cat 30)   (10 20 30 T)CL-USER> :apple                            :APPLECL-USER> (defun foo (&key ((:apple a)) ((:box b) 0) ((:cat c) 0 c-supplied-p))   (list :apple :box :cat))FOOCL-USER> (foo :apple 10 :box 20 :cat 30)  (:APPLE :BOX :CAT)CL-USER> (setf :apple 3); No value    :APPLE is a constant and thus can't be set

2.4 混合不同的形参类型

现在我们已经清楚lisp中运用的形参类型就是:必要形参,&optional 形参,&rest 形参,&key 形参,组合的话,他们必须按顺序:先必要形参,&optional形参,&rest形参,&key形参。

&optional & rest 形参顺序,在前面已经说过了,即:&rest 只能盛必要形参和&optional 形参吃剩下来的。

既然&optional & rest 形参顺序定下来了,就剩下他们跟&key的组合了。

无论&optional 还是&rest形参跟&key组合的时候都容易出错,也就是所谓的有肉但是分布均了,所以就罢工了。具体如下分析:

&Optional 与 &key,如下面输出错误信息的函数调用,你不能说经过我这儿,不给我点吃吧,但是:z 被半路劫了以后,你剩下孤零零的,因为等待他的是两个大王,所以最后大王急眼了,3分不开了,就悲催了。但是有个趁火打劫的情况是,这个时候如果&key有两个&optional,那么:z 3 都糊里糊涂的被&optional给半路劫了。所以说最好别把他们放到一块,最好解决办法就是把optional也设置为&key

(defun foo (x &optional y &key z) (list x y z))CL-USER> (defun foo (x &optional y &key z) (list x y z))FOOCL-USER> (foo 1 2 :z 3)     (1 2 3)  CL-USER> (foo 1)            (1 NIL NIL)CL-USER> (foo 1 :z 3) odd number of &KEY arguments,

&rest&key他们两个都是命苦的人,都是吃必要形参跟&optional剩下来的。两个苦命人结合起来会怎么样呢?他们两个的组合不像上面&optional跟&key的结合,他们会出问题;而现在&rest 和 &key都会对参数进行特殊处理,&rest形参收集到一个形参列表中,&key基于关键字被分配到适当的形参中,Common lisp只支持在定义function的时候的顺序是&rest &key. 如果是&key &rest 它会报错说"misplace &rest"

CL-USER> (defun foo (&rest rest &key a b c) (list rest a b c))STYLE-WARNING: redefining COMMON-LISP-USER::FOO in DEFUNFOOCL-USER> (foo :a 1 :b 2 :c 3)  ((:A 1 :B 2 :C 3) 1 2 3)

2.5 函数返回值

从嵌套结构控制结构中脱身:return-from 是一个特殊操作符,它能够立即以任何值(第二个参数)从函数中间返回。

第一个参数是它想要返回的代码块名,改名字不被求值,因此无需引用。

如果后面参数为 当前函数名和想要返回的值,函数会立即以该值推出。

CL-USER> (defun foo (n)   (dotimes (i 10)     (dotimes (j 10)       (when (> (* i j) n) (return-from foo (list i j))))))STYLE-WARNING: redefining COMMON-LISP-USER::FOO in DEFUNFOOCL-USER> (foo 8)              (1 9)


2.6作为数据的函数-高阶函数

使用函数主要是通过名字来调用它们,并且由于函数已经是一种对代码进行抽象的标准方式,因此允许把函数视为数据也很合理。

Lisp不是唯一一个将函数视为视为数据的语言,C使用函数指针,Perl使用子例程引用(subroutine reference,python使用与lisp相似的模式,C#使用了代理(本质是带有类型的指针),而java则使用了笨重的反射(reflection)和匿名机制。

Function 特殊操作符提供一个获取函数对象的方法,因为我们已经了解了很多数据类型,要么是自求值,要么是等待别的对象给他bound,函数对象最为一个特殊的数据类型,我们当然要用特殊方式,function 接受一个实参,并且返回一个与该参数同名的函数,注意这个名字是不被引用的。

#'function 的语法糖,也就是当碰到#'他会自动把式子作为函数对象对待。

CL-USER> (defun foo (x) (* 2 x))FOOCL-USER> (function foo)   #<FUNCTION FOO>CL-USER> #'foo            #<FUNCTION FOO>

上面我们仅仅是获得了一个函数对象,对于任何一个数据类型他肯定都对应一些独特的操作或实现,比如plist/string 都有想应的操作,而函数就是可以通过funcall / apply 调用。


1funcall 第一个实参为函数对象,其余实参被传递到函数中。

CL-USER> (defun plot(fn min max step)   (loop for i from min to max by step do(loop repeat (funcall fn i) do (format t "*"))(format t "~%"))PLOTCL-USER> (plot #'exp 0 4 1/2)**********************************************************************************************************************************************

注意1:第一个fn, 不为#'fn, 因为我前面好像已经说了,形参的话,只能是接受一个对象,而现在#'fn表示的是一个fn的函数对象,会报错“Required argument is not a symbol: #'EXP”

        2:下面同样是用fn (funcall fn i)而不是#'fn ,因为我们是用fn接受一个函数对象,本身fn就是一个函数对象了,因为fn的值就是一个函数对象了。

2apply 参数也是对一定的需求设定的,现在假设有一个列表plot-data,他正好也包含4个参数,第一个为函数对象,继而mix max step。一般情况下我们是把plot-data各个单元拆开,用函数名直接调用。(plot (first plot-data) (second plot-data)(third plot-data)(forth plot-data))

显然太麻烦了,于是乎apply 就出来了,专门针对参数放在列表中的情况。

Apply 第一个参数是函数对象,剩下一个列表对象,他会将函数用在列表中的值上。

(apply  #'plot  plot-data)

Apply 还接受孤立的实参只要最后一项为列表就行。

(apply  #'plot  #'exp  plot-data)

原创粉丝点击