<-- Home |--lisp

011 没人先生学习Lisp函数

没人先生

没人先生一直都在, 但是没人注意到他. 他从头到尾一直都在跟大家一起学习Lisp.

  1. 001 粗鲁先生Lisp再出发
  2. 002 懒惰先生的Lisp开发流程
  3. 003 颠倒先生的数学表达式
  4. 004 完美先生的完美Lisp
  5. 005 好奇先生用Lisp来探索Lisp
  6. 006 好奇先生在Lisp的花园里挖呀挖呀挖
  7. 007 挑剔先生给出终止迭代的条件
  8. 008 挠痒痒先生建网站记
  9. 009 小小先生学习Lisp表达式
  10. 010 聪明先生拒(ji)绝(xu)造轮子

他感觉他跟Lisp中的函数很像, 天天都在, 每次都要用, 却还是有好多东西是不知道的. 他决定要好好学习一下Lisp中的函数.

函数的返回值

函数式编程一个重要的内涵就是值和值之间的替换,这个在Lisp中体现的淋漓尽致.每个函数的调用,放到一个表达式中,都与直接把函数的返回值放在那里是等价的.

函数可以返回任何数量以及任何类型的Lisp对象, 包括函数对象. 当函数返回多个值时, 推荐使用values来构造返回值, 而不是使用list, 当返回值是values, 可以直接使用第一个值, 也可以用multiple-value-bind来绑定返回值.

绑定多个值的方式如下:

1(multiple-value-bind (a b c) (values 1 2 3)
2  (list a b c))

记得我们可以把一个值替换为另外一个值

 1(defun values-func ()
 2  (values 1 2 3))
 3
 4(multiple-value-bind (a b c) (values-func)
 5  (format t "a: ~a, b: ~a, c: ~a~%" a b c))
 6;; a: 1, b: 2, c: 3
 7;; NIL
 8
 9(multiple-value-list (values-func))
10;; (1 2 3)

当我们直接使用上面的values-func时, 只会返回第一个值, 也就是1.

1(values-func)
2;; 1

如果我们使用explore-lisp来看Common-Lisp中的函数定义, 哪些返回空值的函数, 会返回(values), 而返回多个值的函数, 会返回(values ...). 照着这个来抄总是没错的.

就比如, floor函数的返回值是两个, 第一个是integer, 第二个是real, 这个是通过values来返回的. 请仔细看Delcared typeDerived type两个字段的描述.

 1(describe 'floor)
 2COMMON-LISP:FLOOR
 3  [symbol]
 4FLOOR names a compiled function:
 5  Lambda-list: (NUMBER &OPTIONAL (DIVISOR 1))
 6  Declared type: (FUNCTION (REAL &OPTIONAL REAL)
 7                  (VALUES INTEGER REAL &OPTIONAL))
 8  Derived type: (FUNCTION (T &OPTIONAL T)
 9                 (VALUES (OR NULL INTEGER) NUMBER &OPTIONAL))
10  Documentation:
11    Return the greatest integer not greater than number, or number/divisor.
12      The second returned value is (mod number divisor).
13  Known attributes: foldable, flushable, unsafely-flushable, movable
14  Source file: SYS:SRC;CODE;NUMBERS.LISP
15NIL

最后,我们还可以直接使用nth-value来获取返回值的第n个值.

1(nth-value 1 (values 1 2 3))
2;; 2

这个函数就很好地配合函数式编程的思想,可以很容易进行值和表达式的替换,而不需要引入额外的变量.

函数的参数

函数的参数定义方式:

  1. 必须参数;
  2. &optional可选参数;
  3. &key关键字参数;
  4. &rest剩余参数;
  5. &allow-other-keys允许其他关键字参数;

具体语法和使用方法可以参考CLCB中,这本书还有中文版.

1(defun test-func (a &optional b c &key d e &allow-other-keys)
2  (list a b c d e))

这里的optionalkey可以同时使用,SBCL可能会报警,但是不影响使用.

产生函数

根据CLHS的定义,函数是一种表示当提供恰当个数的参数即可以执行的代码的对象.函数有几种来源:

  1. 函数表达式:function special form;
  2. 函数转换:function coerce
  3. 函数编译:function compile

函数special form

special form是一种特殊的语法形式, 这玩意看起来像是函数, 但却是解释器特殊处理的语法结构. 例如ifcond就是special form. 得益于聪明先生的聪明举动,可以在special operator中查看所有的special form, 一共有25个.

这里面的有一个function,是一个special form, 用来产生函数对象的.

1(function name)

或者使用#'来简写:

1#'name

因为Lisp的函数和变量可以用一样的符号,所以这个#'就是为了区分函数和变量的.

此外,function还可以接受一个lambda来产生一个函数对象.

1(setf func (function (lambda (x) (+ x 1))))
2(func 1)
3;; 2

总的来说,这个很平凡,我们记得在访问函数的时候明确使用#'就可以了.

函数coerce

这个函数是强制类型转换的意思,可以将一个对象转换为另外一个对象. 当我们使用coerce来转换一个函数对象时,会返回一个函数对象.

If the result-type is function, and object is any function name that is fbound but that is globally defined neither as a macro name nor as a special operator, then the result is the functional value of object. If the result-type is function, and object is a lambda expression, then the result is a closure of object in the null lexical environment.

1; 第一种情况,把一个函数名(不能是宏名或者特殊操作符)转换为函数对象,结果返回的是一个函数对象.
2(coerce 'car 'function)
3
4; 第二种情况,把一个lambda表达式转换为函数对象,结果返回的是一个**闭包**,这个闭包是在一个空的词法环境中的.
5(coerce #'(lambda (x) (+ x 1)) 'function)

函数compile

大部分函数都是表现为这一类,例如,文件被加载, 其中的函数就是这样,还可以直接调用compile来编译一个函数.

1(defun test-func (x)
2  (+ x 1))
3
4(compile 'test-func)
5
6(compiled-function-p #'test-func)
7;; T

函数调用

调用函数的三种方式:

  1. funcall;
  2. apply;
  3. multiple-value-call.

funcall

funcall调用一个函数对象时,第一个参数是函数对象,后面的参数是函数的参数.

1(funcall #'(lambda (x) (+ x 1)) 1)
2;; 2

apply

apply调用一个函数对象时,函数对象之后的参数是一个列表,这个列表的元素是函数的参数.

1(apply #'(lambda (x y) (+ x y)) '(1 2))
2;; 3

multiple-value-call

multiple-value-call调用一个函数对象时,把每一个values都分别展开,然后作为参数传递给函数.

1(multiple-value-call #'(lambda (x y) (values (+ x y) (- x y))) 1 2)
2;; 3 -1
3
4(multiple-value-call #'+ (values 3 4) 2 1 (values 8 7 3))
5;; 28

总结

  1. 函数是一种对象,可以返回任何数量以及任何类型的Lisp对象, 包括函数对象;
  2. 函数的返回值推荐使用values来构造,此时直接调用返回第一个值,可使用multiple-value-bind来绑定返回值,或者使用nth-value来获取返回值的第n个值;
  3. 函数的参数定义方式有必须参数,可选参数,关键字参数,剩余参数,允许其他关键字参数;
  4. 函数的产生方式有函数表达式,函数转换,函数编译;
  5. 函数的调用方式有funcall, apply, multiple-value-call;
  6. 是谁学习了上面这些? 没有人.

文章标签

|-->lisp |-->function |-->values |-->apply |-->funcall |-->multiple-value-call |-->教程


GitHub