从Hello World到defmacro,那些让我惊了个呆的代码!

前言

自从看到那个征文活动便灵感突现,这是个为大家介绍Lisp语言的机会,也是个赞扬最让我心动的语言的机会。

毕竟还是学生党,还未有太多时间来学习它,但内心满满的都是热爱与兴奋。文中如有疏漏,还请各位指教!

一次偶然在《黑客与画家》第二版中了解到这门神奇的语言,瞬间便被”洗脑“,立刻找到一大堆资料,前前后后的兴奋的学了几个月,无奈于就业压力,还是选择先将C++/Java等作为主力。

这篇文章主要面向没见过Lisp语言的同学,否则就会觉得这些太简单了,Lisp的博大精深也不是能三两句话讲个明白的。

我并不喜欢讲废话,所以,开始吧!

Hello World

曾看到有人将Common Lisp的Hello World程序来同C++、Java等做对比。在这里并不需要函数或方法,更不需要类,一行代码足矣:

CL-USER> "hello, world"
"hello, world"

那么这是怎么回事呢?因为在这里字符串有着Lisp能够理解的字面语法并且它是自求值对象。

你觉得这没有打印(Print)出来,所以还不完整么?没问题:

CL-USER> (format t "hello, world")
hello, world
NIL

NIL可不是表示出了错,而是像你们所知道的”return 0;“一样是FORMAT语句的返回。

你还觉得不服认为没有用到函数?满足你!

CL-USER> (defun hello-world() (format t "hello, world"))
HELLO-WORLD

上面就是函数的定义了,接下来,就让我们使用它来打印吧!

CL-USER> (hello-world)
hello, world
NIL

好了,这种小儿科的hello world就不继续了,来点炫酷的。

100的阶层

看到这个标题可能有同学会想到,”100的阶层,哦,就是1乘以2乘以3,一直乘到100……for循环就能搞定了。”

那么试试呢?

要用int?还是用long?亦或long long?

那1000的阶层呢,10000的阶层呢?

然而在Lisp中,很容易就可以求出来:

CL-USER> (defun fact (n)
            (if (= n 1)
                1
                (* n (fact (- n 1)))))
FACT
(define (fact n)
    (if (= n 1)
        1
        (* n (fact (- n 1)))))
;Value: fact

上面两段代码分别是Common Lisp和Scheme方言的,没错 ,是方言。

但当真能求100的阶层么?有图有真相!

Lambda表达式

C#在2007年发布C# 3.0中引进了Lambda,C++在2011年发布的C++11版中引进了Lambda,Java则在2014年发布的Java SE 8中引进了Lambda。而以Lambda为核心的Lisp则在半个世纪前就用上了这一特性。

Lisp能够以此为基础做些什么呢?

这是一门函数式语言,数学是基础,下面就来看看丘奇计数(由数理逻辑学家Alonzo Church发明,其还发明了λ演算)。

如SICP这本书的练习2.6(相关的习题解见此专栏:SICP练习 )所介绍:在一个对过程做各种操作的语言里,我们完全可以没有数(至少在只考虑非负整数的情况下)。大家编程的时候相比都要用到各种数字,而在这里,我们可以将0和加一实现为:

(define zero (lambda (f) (lambda (x) x)))
(define (add-1 n)
    (lambda (f) (lambda (x) (f ((n f) x)))))

以上这种表示形式就是Church计数。

那么有了0和“加一”该如何定义1呢,其实也不难,对0执行“加一”操作不就等于1了嘛。使用一张之前我博客上用过的图:

所以1就可以用如下定义了:

(define one (lambda (f) (lambda (x) (f x))))

同样,通过

(add-1 one)

还可以来定义出two,以此便可以无限定义下去,无限,无限,无限……

无穷流

既然谈到了“无限”,那怎么能错过无穷流呢?

流,大家自然都用过,但在这里它还能够表示无穷长的序列。下面就是一个承载了所有正整数的流的定义:

(define (integers-starting-from n)
    (cons-stream n (integers-starting-from (+ n 1))))
(define integers (integers-starting-from 1))

其中的integers?starting?from是函数名,在函数内部又调用了其自身,这就是递归了,而Lisp最常用的就是递归。

右边的n则是传入的参数,在函数内部通过(+n1)这个前缀表达式不断的更新参数,最后通过cons?stream来持续构造这个流。

第一段代码定义了一个函数,第二段代码则定义了一个变量,它以1为参数传入integers?starting?from构造出“1、2、3、4……”这一无穷流。

虽然无法打印出来(但这也并不需要诧异,如果真打印出来了,真个地球的纸张也不够吧),但依旧可以取出来。

C系语言中有pointer、有index,而在这里有car和cdr。

car用于取出序列的头部,cdr用于取出序列头部以外的所有部分。所以在此处用car取出的就是1,用cdr取出的就是以2为起始的无穷流。

这篇文章通过新奇的代码以引起同学们的兴趣,虽然体现了Lisp的部分威力,但它能做的远不仅于此。在一个流中加上filter过滤器,便可以制成信号处理系统,此处的信号可以是太多因素了,这俨然已经上升到了工业级。

Loop

Loop Example 1

好吧,我承认,这个小标题不像前面的lambda和无穷流那样吸引人,但是此处的loop可是功能多多哦。

通过in便列出了序列(1234)中的所有数。

CL-USER> (loop for x in (list 1 2 3 4) collect x)
(1 2 3 4)

这并不稀奇,但是再加上by呢。

CL-USER> (loop for x in (list 1 2 3 4 5 6 7 8 9 10 11 12)
              by #‘cdddr collect x)
(1 4 7 10)

介词这么多,再来一个怎么样?用on来构成列表。

CL-USER> (loop for x on
              (loop for y in
                   (list 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)
                   by #‘cdddr collect y)
              by #‘cdr collect x)
((1 4 7 10 13) (4 7 10 13) (7 10 13) (10 13) (13))

Loop Example 2

还不够炫酷?再来!

CL-USER> (loop repeat 10
              for x = 0 then y
              for y = 1 then (+ x y)
              collect y)
(1 2 4 8 16 32 64 128 256 512)

这段代码主要体现了迭代的思想,大家看看如下迭代过程便能明白,其中repeat表示迭代(重复)的次数。

x=0->1->2->4->8->16->32->64->128->256
y=1->2->4->8->16->32->64->128->256->512

还记得斐波那契数列么?将第二个for改成and,利用这种方式来求斐波那契数列是不是拽到没朋友?

CL-USER> (loop repeat 10
              for x = 0 then y
              for y = 1 then (+ x y)
              collect y)
(1 2 4 8 16 32 64 128 256 512)
CL-USER> (loop repeat 10
              for x = 0 then y
              and y = 1 then (+ x y)
              collect y)
(1 1 2 3 5 8 13 21 34 55)

Loop Example 3

用C#的同学对其中的LINQ想必是觉得很厉害了,在这里也有类似的方式。

CL-USER> (loop for i from 1 to 100
            when (evenp i) sum i)
2550

这里的evenp是一个谓词,用于判断i是否是偶数,在这里是累加了1到100的所有偶数,当然你也可以将它们直接打印出来。

(if (loop for i in ‘(1 3 5 7 9)
                  always (oddp i))
             (print "Oh, yeah!"))

"Oh, yeah!"
"Oh, yeah!"

这里的oddp就是判断奇数的谓词了。咦,这里怎么会有两个”Oh,year!”呢,莫激动,前面的是打印,后面的是返回。

有没有同学没有听说过“可编程的编程语言”,这就是Lisp,而正是依靠“宏”它才是可编程的。

在写算法题的时候以下类似的代码是不是非常常用?

#define MAX 10000

它可以被理解为一个微型的宏,最为一个半个世纪历史的语言,Lisp早已将宏做的出神入化了。引用一段话:

当你开始撰写宏时,你需要像语言设计者一样思考。

我们继续从for开始,假设我们想打印出1到8中每个数的平方,你可以这样写:

for(int i=1;i<=8;i++)

但是呢,程序员嘛,就是这么任性,咱自己写一个for吧。天方夜谭?不不不……

CL-USER> (defmacro for (var start stop &body body)
           (let ((gstop (gensym)))
             `(do ((,var ,start (1+ ,var))
                   (,gstop ,stop))
                  ((> ,var ,gstop))
                ,@body)))
FOR
CL-USER> (for x 1 8
           (princ (* x x)))
1491625364964
NIL

上面这个for还有下面的这个random?choice都是前辈Paul Graham所写,在这里作为例子非常合适。

大家知道函数/方法的参数是给定的,但能不能选取其中一个参数进行求值呢?没错,当然可以。

(defmacro random-choice (&rest exprs)
  `(case (random ,(length exprs))
     ,@(let ((key -1))
         (mapcar #‘(lambda (expr)
                     `(,(incf key) ,expr))
                 exprs))))

大神写了厉害的宏,我就来使用大神写的宏吧。

总结

这只是冰山一角罢了。

如你所见,这就是酷炫的Lisp,一门可编程的编程语言,其还有延时求值和惰性求值等特性,你还可以自己加上新的特性甚至制作自己的方言。

另外也顺便将和Lisp最搭的Emacs也贴出来好了,在Linux上用Emacs是再好不过的事了,但在Windows上简直是各种简陋……于是,我用了这货……

My Emacs for Common Lisp -*GNU Emacs*

OK,写了几个小时就点到为此了。这些并非我从许久之前的学习Lisp时所写的代码中复制过来的,而是此时根据记忆按语法难度重新组织的代码。

Lisp方言众多,有Java程序员喜爱的Clojure,也有用于AutoCAD的AutoLISP,更有本文中使用的专攻学术的Scheme以及工业级的Common Lisp。

学编程两年多,浅浅地用过了好多门语言,唯独Lisp最让我心动,喜欢它的强大与完整,喜欢它的炫酷与简洁,喜欢它的古老与小众。

版权声明:本文为 NoMasp柯于旺 原创文章,未经许可严禁转载!欢迎访问我的博客:http://blog.csdn.net/nomasp

时间: 2024-10-29 10:08:25

从Hello World到defmacro,那些让我惊了个呆的代码!的相关文章

Clojure语言十三: 宏

micro的核心作用 由于micro接收的参数可以很复杂,而且不求值,因此可以接收list,在内部转换,加工后执执行,也可以接收完全不是lisp的语法,在内部转换成clojure能执行的list. 这样可以创建自己的DSL,而把clojure宏实现为DSL解析器. micro与function的区别 特点是允许在Clojure evaluate你的list之前,你能够像处理函数一样的处理它, 但是不evaluate. 如果你想把一个表达式作为参数传递给函数,并要求这个参数不求值,这是做不到的.比

如何打造一个伟大的产品4 - 如何驾驭三个木桶模型

如何驾驭三个木桶模型 打造一个产品最考技巧的是需要去学习究竟如何才能把一个产品的各个功能点进行分门别类的放到"三个木桶模型"里面的三个木桶里面,然后还要知道什么时候这些木桶是已经装满了.返回之前提过的一个例子,究竟一个内嵌的投影模块对手机产品来说是属于gamechangers级别的功能还是distractions级别的功能呢?如果是gamechangers级别的话,那么这个功能是否已经足够捕获大量用户的芳心呢?或者你还需要结合另外一个gamechangers级别的功能才能做到这一点呢?

Lisp简明教程

此教程是我花了一点时间和功夫整理出来的,希望能够帮到喜欢Lisp(Common Lisp)的朋友们.本人排版很烂还望多多海涵! <Lisp简明教程>PDF格式下载 <Lisp简明教程>ODT格式下载 具体的内容我已经编辑好了,想下载的朋友可以用上面的链接.本人水平有限,如有疏漏还望之处(要是有谁帮我排排版就好了)还望指出!资料虽然是我整理的,但都是网友的智慧,如果有人需要转载,请至少保留其中的“鸣谢”页(如果能有我就更好了:-)). Lisp简明教程 整理人:Chaobs 邮箱:[

在NewLisp中实现匿名函数的递归

匿名函数在很多语言中的表现形式大概如下: (lambda (n)   (* (+ n 1) (- n 1))) 只有参数列表和函数体,而没有名字.在大部分情况下没问题,但是一旦需要用到递归的话,就有点麻烦了,因为不知道如何去递归的调用一个匿名函数. 在学术界中有一些解决这个问题的办法,其中一个就是Y组合子,但是那个太繁琐,而且难以通过宏自动将一个lambda变成可递归形式,没什么好处. 根据历史经验,目前比较好的办法,就是实现一个操作符,匿名函数通过这个操作符来调用自身: (lambda (n)

如何打造一个伟大的产品7 - 产品使命验证法

产品使命验证法 假设你现在已经可以将你已建立好的对产品的直觉用来把"三个木桶模型"应用到你所处的行业中,你已经可以轻易(且正确地)把功能点归类到不同的木桶里面,那么你已经比大部分产品经理优秀了.但,这还不够.你要知道这个方法其实是存在一定的瑕疵的: 如果你仅仅是通过凭空臆测的直觉来对功能点进行分类的话,那么你将很容易在犯错误之后,被你脑海中另一把声音的花言巧语所说服而自以为做了个正确的决定. 当你在打造该产品的时候,你势必需要对每个大大小小的决定都事必躬亲,因为其他人根本没有东西作为指

软件工程管理的44个教训(译)

看到一篇文章,讲的不错,颇为实用,粗略翻译一下共享之. 原作:http://www.defmacro.org/2014/10/03/engman.html ====================================== Do. 可为 吸引.教养.训导.留住天才.和工程师们聊天,让他们早些将自己的关注吐出来,如果可以就及时弥补. 要跟每一个工程师就他们的下个重要事项进行沟通. 当开发团队未能达成一致意见的时候你要做一个破局者. 成为信息中心,你要知道每个工程师在做什么,帮助他们处理遗

Lecture Notes: Macros

原论文链接失效.特在这里保存一份 http://www.apl.jhu.edu/~hall/Lisp-Notes/Macros.html Lisp functions take Lisp values as input and return Lisp values. They are executed at run-time. Lisp macros take Lisp code as input, and return Lisp code. They are executed at compi

[Elixir009]像GenServer一样用behaviour来规范接口

1.Behaviour介绍 Erlang/Elixir的Behaviour类似于其它语言中的接口(interfaces),本质就是在指定behaviours的模块中强制要求导出一些指定的函数,否则编译时会warning. 其中Elixir中使用到behaviour的典范就是GenServer, GenEvent. 曾经Elixir有一个叫Behaviour的模块,但是在1.1时就已被deprecated掉了,现在你并不需要用一个Behaviour模块才能定义一个behaviour啦. 让我们一步

如何打造一个伟大的产品5 - 别装错了木桶

买家,利益相关者和权威人士 你越早熟悉你所处产业的历史,技术现状,潜在用户的想法,以及竞争对手的前进方向的话,你就越快的能对该产业描绘出一幅清晰的的蓝图,就能越早的为你的产品构画出一个独一无二的愿景.但是需要小心的是,在跟不同的人进行探讨的时候,有时会很轻易的就采纳了一些错误的人的建议的. 假如你决定了去以对讲机的形式去设计你的移动电话,瞄准的目标客户群体是建筑工人,并且你决定销售出去最好的方法是以自上而下的方式方式先去搞定建筑经理.如果你先去和建筑工人谈的话,可能他们会被对讲机的花哨的图标和一