`
iyuan
  • 浏览: 463901 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

lisp初体验-Practical Common Lisp笔记-7.函数

    博客分类:
  • lisp
 
阅读更多
定义一个新的函数
在Lisp中,通过宏defun以下面的结构来定义函数:
(defun name (parameter*)
  "Optional documentation string."
  body-form*)

这里值得注意的是函数的命名规则(lisp的函数名支持短横等字符):如果函数的目的是由a转为b,那么可以命名成:a->b这个形式,不过在lisp的世界中,a-b这种命名更符合lisp的美。作者这里比较了ab,a_b等形式(个人觉得,a_b形式还是靠谱的,尤其是学习多语言之后,可以有效的避免串了)
让我们从之前的hello world入手,解析下lisp中的函数定义方式:
(defun hello-world () (format t "hello, world"))

函数名为hello-world,参数列表为空,函数的主体是一个表达式:
(format t "hello, world")

再来一个复杂些的:
(defun verbose-sum (x y)
  "Sum any two numbers after printing a message."
  (format t "Summing ~d and ~d.~%" x y)
  (+ x y))

函数名为verbose-sum,拥有x,y两个参数,有一段字符串说明,主体有两个表达式,并且第二个表达式的结果会作为函数的返回值。

参数列表
如同上面的verbose-sum函数,是最普通的参数定义,对函数调用方的参数值有着严格的一一对应关系,多了少了都会报错。下面逐个介绍其他类型的参数:
  • 可选参数

这类参数通常出现于存在不同角色调用同一个函数,而他们关注的点不相同的情况下。关键字: &optional,用法:
(defun foo (a b &optional c d) (list a b c d))

效果:
(foo 1 2)     ==> (1 2 NIL NIL)
(foo 1 2 3)   ==> (1 2 3 NIL)
(foo 1 2 3 4) ==> (1 2 3 4)

也支持特定默认值:
(defun foo (a &optional (b 10)) (list a b))

效果:
(foo 1 2) ==> (1 2)
(foo 1)   ==> (1 10)

在某些应用中,或许也会存在选择参数的默认值与特定参数保持某种关系的需求:
(defun make-rectangle (width &optional (height width)) ...)

针对默认值这种情况,或许有需要知道是否是使用的默认值的需求,那么会有这样一个字段来甄别:-supplied-p,用法如下:
(defun foo (a b &optional (c 3 c-supplied-p))
  (list a b c c-supplied-p))

效果:
(foo 1 2)   ==> (1 2 3 NIL)
(foo 1 2 3) ==> (1 2 3 T)
(foo 1 2 4) ==> (1 2 4 T)

  • 无限制参数

偶尔,也会有这样的需求,允许传入n个参数,n为非负整数。例如神奇的加法运算:
(+)
(+ 1)
(+ 1 2)
(+ 1 2 3)
...

你可以试试,这里也有一个字段对应: &rest ,用法:
(defun format (stream string &rest values) ...)
(defun + (&rest numbers) ...) 

  • 关键字参数

这也是比较常用的参数形式(个人感觉极大的方便了代码的阅读),给参数一个更有意义的命名,由&key来实现:
(defun foo (&key a b c) (list a b c))

关键字参数同时也拥有可选参数的一些特性:
(foo)                ==> (NIL NIL NIL)
(foo :a 1)           ==> (1 NIL NIL)
(foo :b 1)           ==> (NIL 1 NIL)
(foo :c 1)           ==> (NIL NIL 1)
(foo :a 1 :c 3)      ==> (1 NIL 3)
(foo :a 1 :b 2 :c 3) ==> (1 2 3)
(foo :a 1 :c 3 :b 2) ==> (1 2 3)

(defun foo (&key (a 0) (b 0 b-supplied-p) (c (+ a b)))
  (list a b c b-supplied-p))

(foo :a 1)           ==> (1 0 1 NIL)
(foo :b 1)           ==> (0 1 1 T)
(foo :b 1 :c 4)      ==> (0 1 4 T)
(foo :a 2 :b 1 :c 4) ==> (2 1 4 T)

关键字参数还能绑定为其他关键字,例如:
(defun foo (&key ((:apple a)) ((:box b) 0) ((:charlie c) 0 c-supplied-p))
  (list a b c c-supplied-p))

(foo :apple 10 :box 20 :charlie 30) ==> (10 20 30 T)

这种方式用处独特,不过不常见。
  • 混合参数

正常来说,在一个函数中同时出现上面几种参数类型是不常见的,通常会导致一些奇怪的问题:
(defun foo (x &optional y &key z) (list x y z))

(foo 1 2 :z 3) ==> (1 2 3)
(foo 1)  ==> (1 nil nil)
(foo 1 :z 3) ==> ERROR

忠告就是:如果有optional参数和key参数混用,最好全改成key形式,所谓安全又好用。
这样的混合也会出人意料的:
(defun foo (&rest rest &key a b c) (list rest a b c))

(foo :a 1 :b 2 :c 3)  ==> ((:A 1 :B 2 :C 3) 1 2 3)

so,混用参数需谨慎!

函数的返回值
就目前为止,俺们接触到的函数返回值都是由函数体的最后一个表达式自动返回的,这也是最常见的。不过通常也有函数体执行部分就直接返回的需求,这可以用特别字段RETURN-FROM来实现:
(defun foo (n)
  (dotimes (i 10)
    (dotimes (j 10)
      (when (> (* i j) n)
        (return-from foo (list i j))))))

以上代码即是获取符号目标的最小的i,j值。需要简单注意下RETURN-FROM字段的用法,它其后跟着的是函数名,然后是返回值。或许有些麻烦,这个在后面章节会有专门介绍。

高阶函数
这个名词意思与数学不太一样,主要是指"函数的参数也是函数"这么一种情况。
在lisp中,函数本身也是数据,是允许传递运行的。这里涉及到一个特殊字段:FUNCTION
例如:
(defun foo (x) (* 2 x))

则可以通过FUNCTION来获取函数:
(function foo)
#<Interpreted Function FOO>

FUNCTION还有一个缩写:#'。而在获取了函数后,有两个字段可以用于调用它:FUNCALL,APPLY,这二者之间仅在传递参数方式上有所区别。
funcall用于确定传递参数的个数及值的情况下:
(foo 1 2 3) === (funcall #'foo 1 2 3)  //二者等价

在以下的应用中,funcall更有意义:
(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 "~%")))

效果:
 (plot #'exp 0 4 1/2)
*
*
**
****
*******
************
********************
*********************************
******************************************************
NIL

相对于funcall一次传一个参数,apply就简单多了,它允许传参数列表:
(apply #'plot #'exp plot-data)

这里最好写个实例来印证下。

匿名函数
某些情况下,我们需要用些功能,不过用defun来定义又有些"牛刀杀鸡"的感觉,于是lambda就出现了,定义一个匿名函数:
(lambda (parameters) body)

用法:
(funcall #'(lambda (x y) (+ x y)) 2 3) ==> 5
((lambda (x y) (+ x y)) 2 3) ==> 5

与普通函数的对比:
(defun double (x) (* 2 x))
(plot #'double 0 10 1)
-----
 (plot #'(lambda (x) (* 2 x)) 0 10 1)

关于匿名函数的用法,在后面的章节还会陆续给出。

(未完待续)
0
2
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics