在REPL中实验
Lisp中最简单的表达式就是一个数字。输入10会返回10:
CL-USER> 10
10
Lisp的读取器在REPL中的R,读取了”10”的文本后,会创建一个Lisp对象表示数字10。这个对象是一个self-evaluating(自求值)对象,意思是把它给求值器在REPL中的E时,得到的值就是它本身。这个值再给打印器,所以在它后面打印出10。
输入(+ 2 3):
CL-USER> (+ 2 3)
5
在圆括号之中的任何东西都是一个列表,这里的表有3个元素,+符号,数字2和3。在通常情况下Lisp把第一个元素看作是函数的名字,剩余的元素当作表达式传给函数的参数求值。在这里+符号是执行加法的函数。对2和3求值就把它们传入加法函数,得到5。5这个值就被输出。
“Hello, World,”Lisp风格
在编程书籍中都有的“hello,world”程序,也能很容易的让REPL打印出“hello,world。”
CL-USER> “hello, world ”
“hello, world”
字符串也像数字一样,被Lisp理解为一个原义语法都是自求值对象:Lisp读取双引号之间的字符串,并在内存中实例化为字符串对象保存,求值的时候它自身当作原义语法输出。在内存中双引号不是字符串对象的一部分——它们只是语法告诉读取器读取字符串。输出器把它们原样输出的时候是因为输出对象与读取对象都是理解为同样的语法。
不过严格来说这不是一个真的“hello,world”程序。它更像是一个“hello,world”值。
能够用一个真的程序由侧面效果输出字符串“hello,world”到标准输出。Common Lisp提供了几个方法输出,但是FORMAT函数最灵活。FORMAT有可变参数的列表,但只有两个必要的参数是输出目标和字符串。下一章会介绍字符串能包含嵌入的指令类允许把后面的参数插入到字符串中和printf或者Python的字符串-%一样。如果字符串不包含~,它会按他原始的输出。如果用t作为第一个参数,它会输出到标准输出。所以FORMAT表达式会这样输出“hello,world”:
CL-USER> (format t “hello, world”)
hello, world
NIL
需要注意的一件事是FORMAT表达式的值是NIL在“hello,world”的后面一行输出。NIL是对FORMAT求值的结果,NIL是Lisp版本的false和null。与之前看的其他表达式不同,FORMAT表达式的侧面效果更有趣——在这里输出到标准输出——超过了它的返回值。但是所有Lisp的表达式都会被求值并返回一些结果。
不管怎样,对是否是一个真的“程序”仍然存在争论。但是,我们已经看到由REPL支持的自底向上的编程风格:能用不同的方法实验并对对已做的测试做出解释。现在我们有一个简单的表达式完成了我们需要的,我们只需要把它放入一个函数。函数是一个基本的程序构建块,在Lisp中能用DEFUN表达式定义:
CL-USER> (defun hello-world () (format t “hello, world”))
HELLO-WORLD
在DEFUN后面的hello-world是函数的名字。在第4章我们会看到哪些字符能被用作名字,但是现在可以说很大部分字符都能满足,像-,在其他语言中是非法的但是在Common Lisp是合法的。这是标准的Lisp风格——更不必说在普通的英语印刷中的一行——是由连字符合成一个名字,像hello-world,而不是用下划线像hello_world,或者大写字母像helloWorld。在名字后面的圆括号()限定参数列表,在这里因为这个函数没有参数所以是空。剩余的部分是函数的主体。
在1级这个表达式和其他所有看到的都一样被REPL读取和求值再打印结果。在这里返回值是刚定义的函数名字。但是像FORMAT这样的表达式更在意的是侧面效果而非返回值。不像FORMAT表达式,侧面效果是不可见的:当这个表达式被求值,一个名叫HELLO-WORLD的函数主体是(format t “hello, world”)的新函数被创建。
一旦定义了函数,我们能这样调用:
CL-USER> (hello-world)
hello, world
NIL
输出结果和FORMAT的表达式求值一样,包括NIL值。在Common Lisp中函数自动返回对最后一个表达式求值的结果。