本文描述如何使用Lisp工具集搭Z个完整的个h博客站点。一个搭建好的例子站点可以参看我的个人博客:http://codemacro.com?/p>
要搭Z个独立博客,需要两斚w的支持。一是博客YӞ二是Ҏ选择的博客Y件取得必ȝ“g“。例如我q里使用的是Lisp工具集,需要一个可以完全控制的服务器,所以这里我需要一个VPS。当Ӟ购买一个合适的域名也是必须的。以下将针对q些内容做描q?/p>
VPS提供商国内国外都有很多。我选择的是 rapidxen Q?28M内存Q?q?0来美元,是国外比较便宜的,速度上还q得厅R?/p>
购买了VPS后,可以q入后台理面安装VPS操作pȝ。同P因ؓ我用的是LispQ我选择安装了Debian 6.0 squeeze (minimal)64位。实际上我更們?2位,因ؓ我的PCpȝ是32位,方便试。安装系l非常简单,基本随意讄下即可。值得注意的是Q除了修改root密码外,最好修改下ssh端口Q具体设|方法可以另行搜索。此外,因ؓ后面我会使用nginx作ؓHTTP前端服务器,Z方便安装nginxQ最好更C软g源列表,~辑etc/apt/source.list:
deb http://ftp.us.debian.org/debian squeeze main
deb http://packages.dotdeb.org stable all
deb-src http://packages.dotdeb.org stable all
deb http://php53.dotdeb.org stable all
deb-src http://php53.dotdeb.org stable all
购买VPS最主要的,是获取C个独立IPQ如图:
然后可以去购买域名。同P也有很多域名服务商。这里我选择的是 godaddy Q我选择的域名codemacro.com一q?1元。购C域名后,需要将域名和VPS IP兌h。详l设|也可以另行搜烦。这里简要提下:在成功登入godaddy后,选择My AccountQ进入自q域名Q选择DNS ManagerQ然后添加域名映即可,如图Q?/p>
通过以上讄后,你购买的域名成功指向你购买的VPS地址了。可以通过ping来观察是否指向成功?/p>
要在VPS上安装YӞ首先需要SSH上你的VPSQ例如:ssh -p 1234 root@codemacro.com?/p>
q里使用的Y仉包括Q?/p>
实际上,可以完全使用Lisp作ؓWeb服务器,但我担心效率问题Q对个h博客而言完全没这回事Q,所以用了nginx作ؓWeb服务器前端,hunchentoot攑֜后面?/p>
在设|好debian软g源后Q安装非常简?
apt-get install nginx
安装完后Q因HTTPh转发lLisp服务器,所以需要修改下配置:
vi /etc/nginx/sites-avaiable/default
?hz֏lLisp服务器(假设监听?000端口Q?
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
然后可以启动nginx?
nginx
q个时候通过览器访问,会得?03 bad gateway的错误提C,因ؓhunchentootq没开启?/p>
SBCL 同样可以通过apt直接安装:
apt-get instal sbcl
装好SBCL后,可以进一步安?quicklisp 。可以完全遵守quicklisp官方l的安装Ҏq行安装。大概就是先获取quicklisp.lisp文gQ然后在SBCL中蝲入,Ҏ提示卛_。这里不再赘q?/p>
安装好quicklisp后,可以用它安装很多Lisp软g/库了。quicklisp在安装一个Lisp库时Q会自动下蝲q安装依赖库Q就像apt-get一栗因为ext-blogq未收入到quicklisp的Y件列表里Q所以ext-blog需要手动安装。首先,在本圎ͼ非VPS上)获取ext-blog源码:
git clone git://github.com/kevinlynx/klprj.git
上面的git是我个h存东西用的,暂时没将ext-blog单独攄。进入到ext-blog目录。该目录下有几个方便的脚本可以用于博客管理。首先将ext-blog打包q上传到VPS上,例如:
./upload-dist.sh root@codemacro.com 1234 /home/test
该脚本会调用make-dist.shext-blog全部源码打包Q然后用scp拯该文件及update-blog.sh到VPS指定的目录里Q这里是/home/testQ,然后ssh上VPS。期间会两次输入VPSpȝ的密码。然后以下操作将在VPS上完成?/p>
首先q入到刚才拷贝文件的目录:
cd /home/test
解压ext-blog.tar.gz:
tar xvf ext-blog.tar.gz
然后ext-blog被解压到/home/test/dist目录。进入此目录q行SBCL:
cd dist
sbcl
ext-blog目录下dep.lisp会用quicklisp安装依赖库,q入SBCL后,载入该文件即可安装所有依赖库Q这可能需要一Ҏ?
(load "dep.lisp")
在没有其他问题下Q可以暂旉出SBCL完成一些其他准备工作?/p>
ext-blog在最q的版本中加入了验证码生成功能,q需要一个pcf字体文g。因为字体文件一般较大,所以upload-dist.sh脚本q没有将该字体文件打包,所以这里需要手动复Ӟ同样在本地的ext-blog目录?
scp -P 1234 data/wenquanyi_12ptb.pcf root@codemacro.com:/home/test/dist/data/
另外Q因为需要将Lisp解释器放|在pȝ后台执行Q避免关掉SSH会话后终止SBCLq程Q所以这里需要个工具gnu screen。可以用apt-get来安?
apt-get install screen
然后Q一切就OK了。在VPS上可以用ext-blog目录下的run-blog.sh来运行这个博客(首先定VPS上的nginx开启):
./run-blog.sh
该脚本会使用screen在后台开启一个SBCLq程Qƈ自动载入ext-blogQ然后在8000端口上开启HTTP服务。这个启动过E可能会使用几十U的旉Q直接ctrl+z退出screenQ这q不l止SBCL。一D|间后便可在浏览器里测试?/p>
如果一切正常,此时通过览器访问你的站ҎQ会被重定向C个博客初始化面Q如下:
上图中我是在本机试的,所以域名是localhostQ希望不至于产生误解。初始化仅需输入用户名和密码卛_Q以后可通过该用户名和密码进入博客后台管理页面。完成这一步后Q就可以q入博客后台理面做更多的讄Q例如博客标题等?/p>
ext-blog的管理页面用了emlog博客pȝ的CSS及其他资源,因此有同学觉得管理页面很面熟׃奇怪了。ext-blog提供在线~辑博客功能Q同时也支持单的metaweblog APIQ因此可以用一些博客客L来发表文章(仅测q我自己写的博客客户端cl-writerQ?/p>
本文描述较ؓ_略Q主要是很多l节我自׃C清。如有问题可以发邮gl我?/p>
ext-blog是一个用Common Lisp~写的博客系l。基于之前基于nuclblog修改的经验,新的ext-blog最大程度地博客本w的逻辑与前台渲染分dQƈ且添加了对主?(theme)的支持。制作新的主题可以随便找一个WordPress的主题,然后php代码译成Lisp代码卛_?/p>
ext-blog底层代码非常,其实基本的博客系l功能本来就不多。大部分功能都是?月初完成。那个时候公司每天加班,下班回去后还写点Lisp代码。后来越整越累,实在没那完善它的心情,一拖就拖到7月底Q功能都q不完善(臛_q得加个rss导出吧?Q?/p>
ext-blog主要有几个页面派发,Ҏ个页面都z֏l具体的主题模块Q让其完成渲染。编写一个主题本质上是生成html面。在Lisp的世界中有很多库可以生成html。ext-blog的主题也不限制你使用哪一个html生成库。目前我自己UL?个WordPress主题Q用的都是google的closure-template的LispUL版本Q即cl-closure-template。closure-template会从模板产生?Lisp函数Q这一Ҏ比同cd中的html-template方便一炏V当Ӟ作ؓ一个模板语aQ内|判断、@环则是必ȝ?/p>
世界上很多流行的语言都有行的Web开发框架。Lisp斚wQ我最开始选用的是WeblocksQ我甚至用它为公司写了个单的订餐pȝQ这让一个程序员颇有自豪感)。但l究觉得Weblocks太难用,复杂Q但没有实际功能。我甚至阅读了它80%的源代码Q但依然获取不到如何更好使用它的思想。然后恰好我看了些Rails例子Q虽然我不懂Ruby语言Q依然可以看到很多语aҎ有Lisp的媄子)Q但看懂例子q不是大问题。后来我军_自己写个 Web框架Q因为其实我主要需要的是一个urlz֏(route)Q就像Rails那样。我甚至为此做了些详l设计,l果后来不幸发现Lisp里已l有一个类似的框架了,q就是Restas。ext-blogZRestas?/p>
后台理q东西其实可要可不要。就没有后台管理,也可以通过增强RPC来实现。但q不是每个h都是LisperQ相信想了解ext-blog的h很大一部分都是惛_习Lisp的h。综合来看,拥有一个后台管理功能,提供更友好的操作界面Q也是非常有必要的。但我确实不擅长做前台美化的工作。幸q地是我渲染和逻辑分离开了,后台理也算是主题的一U。然后,我抄了emlog博客pȝ的后台管理,如前所_也就是把php代码Q虽然我也不懂phpQ翻译成lisp代码?/p>
ext-blog是完全有理由发布到common-lisp.net上的Q甚臌可以加入到quicklisp的库列表里。但前提是排除尽可能多的 bugQ写一pd英文文档Q以及最重要的,对其q行长期l护。不q的是我目前没有q个旉和精力。所以,只能暂时在这里发布下了?/p>
要围观效果的L步至我的独立博客Q?a title="http://codemacro.com" >http://codemacro.com。关于ext-blog更正式的介绍L步此:http://codemacro.com/view/8?/p>
psQ之前订阅我独立博客的TXȝ更换下rss地址Q?a title="http://codemacro.com/feed" >http://codemacro.com/feedQ而博客主也最好换?a title="http://codemacro.com" >http://codemacro.com?/p>
Author: | Kevin Lynx |
---|---|
Date: | 3.30.2011 |
Contact: | kevinlynx at gmail dot com |
Tip
本文简要介绍了如何使用Lisp实现一个简单的RSS阅读器,对Lisp无兴趣的TX可以 只对RSS阅读器的实现思路稍作了解即可。
RSS Reader的实现并不像它看上去那么复杂。当初在决定写这个作为Lisp练习时,甚至觉得 没有多少内容可做。其简单程度甚至用不了你启动一个慢速IDE的时间:D。对Lisp无兴趣的 TX只需要读完这一节即可,
RSS在实现上,可以说是XML的又一次扩张式的应用。因为RSS最重要的东西就是一个XML文件 。RSS主要用于Web中的内容同步。例如我们写的博客,门户网站的新闻,都是内容。Web服 务器将这些内容组织成XML,然后我们通过一个客户端来解析这些XML,就可以在不用直接访 问网站的情况下获取信息:
RSS阅读器就是这样一个从Web服务器通过RSS(表现形式为XML)来获取信息内容的工具。它 可以被实现为一个独立的客户端程序,也可以实现为像Google Reader这种网页形式。后者 其核心功能其实是Google服务器在做,取得信息后再发给用户。
上已提及,RSS的实现其实就是个XML文件。这个XML文件格式非常简单,例如:
<?xml version="1.0"?> <rss version="2.0"> <channel> <title>Liftoff News</title> <link>http://liftoff.msfc.nasa.gov/</link> <description>Liftoff to Space Exploration.</description> <item> <title>Star City</title> <link>http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp</link> <description>Oh no, you wrote another blog!</description> </item> </channel> </rss>
我们身边到处都是RSS文件,例如 http://m.shnenglu.com/rss.aspx 。RSS文件的框架大 致为:
<rss> <channel> <item> </item> <item> </item> ... </channel> </rss>
对,其框架就是这样,一个channel节点,其下若干个item节点。举例来说, CPPBLOG首页就 是一个channel,该channel下有若干原创文章,每篇文章就是一个item。 无论是channel ,还是item,都会有很多属性,例如title/description/link,有些属性是RSS规范里要求 必须有的,有的是可选的。
那么,服务器和客户端是如何交互的呢?首先,服务器上的程序针对其某个页面,生成对应 的RSS文件。这个RSS文件基本上是有固定的URL的。客户端每次获取内容时,就从这个固定 的URL获取这个RSS文件。客户端获取到这个RSS文件后,解析之,再呈现给用户。这就是整 个过程。这个过程中客户端与服务器的交互,全部是正常的HTTP请求。
而RSS阅读器,如果做得足够简单,则只需要从指定的地方获取到RSS文件,然后解析这个 XML文件,然后以相对友好的形式显示即可。
虽然RSS阅读器在核心功能上很简单,但是作为一个可以使用的工具,依然有很多功能点需 要实现。基本功能包括:
我们还可以做很多扩展,例如Google Reader之类的在线RSS阅读器。这些阅读器的RSS抓取 功能做在服务器端,它们除了上面提到的基础功能外,还会包含内容分类,给内容打一些 标签,分析用户的订阅习惯然后推荐类似的内容等等。
本节描述在Lisp中实现上文的内容。主要包括: 通过HTTP请求获取到RSS文件、解析RSS文件 。
Lisp虽然历史悠久,但其扩展库标准却做得很拙劣。偏应用级的扩展库要么由编译器实现提 供,要么就得自己在网上找。一方面使用者希望库使用起来方便,另一方面库开发者在跨编 译器实现方面也头疼不已。所幸现在有了quick lisp,安装第三方库就像Ubuntu里安装软件 一样简单(大部分)。
socket支持就是面临的第一个问题。不过我这里并不考虑跨编译器实现的问题,直接使用 SBCL里的socket接口。
要获取RSS文件,只需要连接Web服务器,发起HTTP的GET请求即可。当然,建立TCP连接,组 建HTTP请求包,就不是这里的讨论了。我们还是拿CPPBLOG首页的RSS为例,该RSS的URL为:
http://m.shnenglu.com/rss.aspx
拆分一下,得到host为m.shnenglu.com(即我们要connect的地址),rss的uri为 /rss.aspx(即HTTP请求里的文件URI),于是建立HTTP请求包:
GET /rss.aspx HTTP/1.0 Host: m.shnenglu.com
关于HTTP请求的一些基础知识,可以参考我很早前写的一篇博客:<实现自己的http服务器>。 正常情况下,Web服务器就会返回RSS的文件内容。然后我们就可以继续解析。
RSS本身是一个XML格式的文件。之前连接Web服务器发起HTTP请求没有用到第三方库,但是 解析XML文件不是几十来行代码能搞定的事情,所以这里需要选用一个第三方库。
我用的是s-xml,这个库在我之前的 关于Lisp的文章 中提到过。s-xml与我之前在C++ 领域见到的XML解析库最大的不同点在于,它提供的API是基于事件模式的。意思是说,你不 要去查询某个element的值是多少,当我解析到的时候会告诉你。事件模式的编程方式自然 离不开回调函数:
(s-xml:start-parse-xml stream (make-instance 's-xml:xml-parser-state :new-element-hook #'decode-rss-new-element :finish-element-hook #'decode-rss-finish-element :text-hook #'decode-rss-text)))
与s-xml交互的也就是上面代码里提到的三个函数:new-element-hook, finish-element-hook , text-hook。这种类型的接口导致解析代码大量减少,但不利于理解。我们要在整个解析 过程中传递数据,需要通过与s-xml交互的函数参数(当然不会蠢到去用全局变量)。
解析过程中通过往函数参数指定的对象身上塞数据完成,整个解析实现也就几十行代码。 文章尾可下载代码看看。
通过上面两步,我们得到了RSS文件、解析出了具体内容,最后一步就是呈现出来看看。RSS 文件里每个Item都是一篇文章(新闻之类),这个文章内容可直接包含HTML标记,说白了, 这些内容就是直接的HTML内容。要显示这些内容,最简单的方法就是把一个RSS转换成一种 简单的HTML文件,以供阅读。
这里就涉及到HTML generator,几乎所有的Lisper都会写一个HTML产生器(库)(虽然目前 我还没写)。这种库的作用就是方便地输出HTML文件。
Lisp相对于其他语言很大的一个特点,或者说是优点,就是其语言本身的扩展能力。这种扩 展不是简单的添加几个函数,也不是类,而是提供一些就像语言本身提供的特殊操作符一样 的东西。而HTML generator正是这种东西大放异彩的地方。这种感觉有点像在C++中通过模 板造出各种增强语言特性的东西一样(例如boost/loki)。
因为我这里只是输出简单的HTML文件,何况我对HTML的标记了解的也不多,也懒得再花经历 。所以我暂时也就将就了些土方法:
(with-output-to-string (stream) (let ((channel (rss-channel rss))) ;取出channel对象 (format stream "<html><head><title>~a</title></head>" (get-property channel :|title|)) ;取出channel的title
最后组合一些接口,即可将整个过程联系起来,导出html文件:
(cl-rss-test:test-rss-http :uri "/news/newshot/hotnewsrss.xml" :host "cd.qq.com")
然后在浏览器里查看,如图:
;;EOF;;
Author: | Kevin Lynx |
---|---|
Date: | 3.21.2011 |
Contact: | kevinlynx at gmail dot com |
Note
本文描述的Lisp主要指Lisp的方aCommon Lisp?/p>
变量Q是所有编E语a里都有的语法概念。在C/C++中,变量用于标示一个内存地址Q而变 量名则在语法层面上代表这个地址。当链接器最l链接我们的E序Ӟ将q些名字替换 为实际的地址。在其他语言中,变量虽然或多或少有其他不同的含义Q但也大致如此?/p>
Lisp中的变量也差不多q样Q但若将variable和Lisp中的 symbol 攑֜一P则多会 带来些困惑?/p>
很多教授Lisp的书中,大概会简单地告诉我们可以使用如下的方式定义一个全局变量 [1].
(defparameter *var* 1)
如上代码Q我们便定义了一个全局变量 *var*[2] Q它被初始化为数?。同P我们 q可以用另一U基本相同的方式:
(defvar *var* 1)
除了全局变量Q我们还可以定义局部变量。但局部变量的定义E显ȝQ却可能是另一U? 设计考虑Q。定义局部变量需要用一些宏Q或者特D运符Q例?
(let ((var 1)) (format t "~a" var))
好了Q就q些了。Lisp中关于变量的l节Q也p些。你甚至能用你在C/C++中的l验来窥 探一切。但是,我们很快qC很多困惑的地斏V?/p>
我遇到的W一个困惑的地方来源于函敎ͼ那么{我讲讲函数再来分n下坎块?/p>
Lisp中的函数l对不复杂,你绝对不用担心我在忽悠你 [3] 。作Z门函数式语言Q其首要 d是加强函数q个东西在整个语a里的功能。如果你喜欢qK各种与你工作不相q的 技术,你肯定已l对很多函数式语a世界中的概念略有耳闻。例如闭包,以及first class type [4] ?/p>
Lisp中的函数是first class type。这什么意思呢Q直白来_ Lisp中的函数和变? 没什么区别,享有同等待遇 。进一步来_变量fn的值可以是数?Q也可以是字W串 "hello"Q甚x某个函数。这其实是C++E序员说的functor?/p>
Lisp中定义函数非常简?
(defun add2 (x) (+ 2 x))
q样Q我们就定义了一个名为add2Q有1个参敎ͼ1个返回值的函数。要调用该函数时Q只需 ?(add2 2) 卛_。这直和我们在Lisp中完成一个加法一模一?(+ 2 3)
Lisp作ؓ一门函数式语言Q其函数也能作ؓ另一个函数的参数和返回?[5]
(defun apply-fn (fn x) (funcall fn x))
apply-fn函数W一个参数是一个函敎ͼ它用funcall函数间接地调用fn指向的函数。作? 一个C++E序员,q简直太好理解了Q这完全是一个函数指针的语法p嘛。于是,假设? 们要使用apply-fn来间接调用add2函数:
(apply-fn add2 2) ;; wrong
可是q是不对的。我们需要通过另一个特D操作符来完成这件事:
(apply-fn #'add2 2) ;; right
#'操作W用于将add2对应的函数取出来Q这么说当然不大准确。AgainQ作Z个C++E序? Q这直就是个取地址操作W?amp;的语法糖嘛。好吧,q么理解hg没问题了?/p>
Lisp中能甚至能在M地方定义一个函敎ͼ例如我们创徏一个函敎ͼ该函数返回创建出来的 函数Q这是一个典型的讲解什么是 闭包 的例?
(defun get-add-n (n) #' (lambda (x) (+ x n)))
无论如何Qget-add-n函数q回一个函敎ͼ该函数是add2函数的泛型实现。它可以你传入 的参数加上n。这些代码里使用了lambda表达式。lambda表达式直白来_是创徏一个字 面上的函数。这又是什么意思呢Q就像我们在代码中写?Q写?hello"一P2是个字 面上的数字,"hello"是个字面上的字W串 [6] ?/p>
那么Q总而言之,通过lambda创徏一个函CQ然后通过#'操作W即可得C个函敎ͼ虽然 没有名字。有了以上知识后QAgain and againQ作Z个C++E序员,很快我们p得到一 个程序:定义变量Q用变量M存一个函敎ͼ然后通过q个变量来调用这个函数。这是多? 天经C的事Q就像之前那个通过参数调用其指向的函数一?
;; wrong (defvar fn #' (lambda (x) (+ x 2))) (fn 3)
q样的代码是不对的,错误发生于第二行Q无Z使用的Lisp实现是哪U,大概会得到如? 的错误信?
"The function FN is undefined."
老实_q已l算是多么有q可循的错误提示了啊。将以上代码和之前的apply-fnҎQ是 多么得神似啊Q可惜就是错的。这是我们遇到的W一个理解偏差导致的问题。如果你q不? 入探IӞ你将会在q一块遇到更多麻烦。及时地拿出你的勇气Q披荆斩,刨根I底Q绝? 是学习编E的好品质?/p>
上文中提到的变量函数之类Q之所以会在某些时候与我们的理解发生偏差,q且L存在? 秘的地Ҏ法解释。这完全是因为我们理解得太片面导致。Lisp中的Symbol可以说就是某 个变量,或者某个函敎ͼ但这太片面。Lisp中的Symbol拥有更丰富的含义?/p>
像很多语言的变量、函数名一PLisp中的Symbol比其他语a在命名方面更自由Q?? 要位?|'字符之间的字W串Q就表示一个合法的Symbol名?/strong> 我们可以使用函数 symbol-name来获取一个Symbol的名字,例如:
(symbol-name '|this is a symbol name|) 输出Q?this is a symbol name"
'(quote)操作W告诉Lisp不要对其修饰的东西进行求?evaluate)。但假如没有q个操作W? 会怎样呢?后面我们看C怎样?/p>
<ANSI Common Lisp>一书中有句话真正地揭示了Symbol的本质: Symbols are real objects 。是的,Symbols是对象,q个对象像我们理解的C++中的对象一P它是一? 复合的数据结构。该数据l构里包含若q域Q或者通俗而言Q数据成员。借用<ANSI Common Lisp>中的一图:
通过q幅图,可以揭开所有谜底。一个Symbol包含臛_图中的几个域Q例如Name、Value? Function{。在Lisp中有很多函数来访问这些域Q例如上文中使用到的symbol-nameQ这? 函数本质上就是取Z个Symbol的Name域?/p>
自然而然圎ͼ阅Lisp文档Q我们会发现果然q有其他函数来访问Symbol的其他域Q例?
symbol-function symbol-value symbol-package symbol-plist
但是q些又与上文提到的变量和函数有什么联pdQ真相只有一个, 变量、函数粗略来 说就是Symbol的一个域Q一个成员。变量对应Value域,函数对应Function域。一个Symbol q些域有数据了,我们说它们发生了l定(bind)?/strong> 而恰好,我们有几个函数可以用于判 定这些域是否被绑定了?
boundp ;判定Value域是否被l定 fboundp;判定Function域是否被l定
通过一些代码来回味以上l论:
(defvar *var* 1) (boundp '*var*) ; q回? (fboundp '*var*) ; q回? (defun *var* (x) x) ; 定义一个名?var*的函敎ͼq回值即为参? (fboundp '*var*) ; q回?
上面的代码简直揭U了若干惊天地泣鬼神的真相。首先,我们使用我们熟知的defvar定义? 一个名?*var* 的变量,初gؓ1Q然后用boundpd?*var* 的Value域是? 发生了绑定。这其实是说Q?原来定义变量是定义了一个SymbolQ给变量赋|原来? 是给Symbol的Value域赋|
其实QLisp中所有这些符P都是Symbol?/strong> 什么变量,什么函敎ͼ都是云。上面的 例子中,紧接着用fboundp判断Symbol *var* 的Function域是否绑定,q个时候ؓ假? 然后我们定义了一个名?*var* 的函敎ͼ之后再判断,则已然ؓ真。这也是Z么, 在Lisp中某个函数可以和某个变量同名的原因所在?/strong> 从这D代码中我们也可以看? defvar/defunq些操作W、宏所做事情的本质?/p>
事情pL束了QOf course not。还有很多上文提到的疑惑没有解决。首先,Symbol?
如此复杂Q那么Lisp如何军_它在不同环境下的含义呢?Symbol虽然是个对象Q但它ƈ不像
C++中的对象一P它出现时q不指代自己Q不同应用环境下Q它指代的东西也不一栗这
些指代主要包括变量和函数Q意思是_ Symbol出现Ӟ要么指的是它的ValueQ要么是
它的Function?/strong> q种背地里干的事情,也算是造成qh的一个原因?/p>
当一个Symbol出现在一个List的第一个元素时Q它被处理ؓ函数。这么说有点qh人,因ؓ
它带q了Lisp中代码和数据之间的模p边界特性。简单来_是当Symbol出现在一个括?
表达?s-expression)中第一个位|时Q算是个函数Q例? 除此之外Q我能想到的其他大部分情况,一个Symbol都被指代为它的Value域,也就是被?
作变量,例如: q看h是多么古怪的代码。但是运用我们上面说的结论,便可L解释Q表辑ּ中第一?
*var* 被当作函数处理,它需要一个参敎ͼ表达式第二部分的 *var* 被当作变?
处理Q它的gؓ1Q然后将其作为参C入?/p>
再来说说'(quote)操作W,q个操作W用于防止其操作数被求倹{而当一个Symbol出现Ӟ
它L会被求|所以,我们可以分析以下代码: q个代码q不正确Q因?*var* L会被求|像 (*var* *var*) 一PW二
?*var* 被求|得到数字1。这里也会发生这U事情,那么最l就{同? 我们试图d数字1的Value域,而数?q不是一个Symbol。所以,我们需要quoteq算W? q句代码是说Q取Symbol *var* 本n的Value域!而不是其他什么地斏V至此,我们
便可以分析以下复杂情? 现在Q我们甚臌解释上文留下的一个问? lfn的Value赋g个函敎ͼ (fn 3) 当一个Symbol作ؓ函数使用Ӟ也就是取?
Function域来做调用。但其Function域什么也没有Q我们试囑ְ一个Symbol的Value域当?
Function来用。如何解册个问题?xQsymbol-function可以取到一个Symbol?
Function? 通过昄地给fn的Function域赋|而不是通过defvar隐式地对其Value域赋|可以
(fn 3) 调用正确。还有另一个问题也能轻易解? 本意是想传入add2q个Symbol的function域,但是直接q样写的话,传入的其实是add2?
Value?[7] Q这当然是不正确的。对比正的写法Q我们甚臌猜测#'q算W就是一?
取Symbol的Function域的q算W。进一步,我们q可以给出另一U写? 深入理解事情的背后,你会发现你能写出多么灉|的代码?/p>
(add2 3) ; add2位于W一个位|,被当作函数处?
(*var* 3) ; q里*var*被当作函数调用,q回3
(*var* *var*) ; q是正确的语句,q回1
(symbol-value *var*) ; wrong
(symbol-value 1) ; wrong
(symbol-value '*var*) ; right
(defvar *name* "kevin lynx")
(defvar *ref* '*name*) ; *ref*的Value保存的是另一个Symbol
(symbol-value *ref*) ; ?ref*的ValueQ得?name*Q再?name*的Value
;; wrong
(defvar fn #' (lambda (x) (+ x 2)))
(fn 3)
(setf (symbol-function 'fn) #' (lambda (x) (+ x 2)))
(fn 3)
(apply-fn add2 2) ; wrong
(apply-fn (symbol-function 'add2) 2)
关于Symbol的内容还有更多,例如Package。正理解这些内容以及他们之间的关系Q有? 于更深刻地理解Lisp?/p>
[1] | 在Lisp中全局变量又被UCؓdynamic variables |
[2] | Lisp中按照习惯通常在ؓ全局变量命名时会加上星号Q就像我们习惯用g_一?/td> |
[3] | 因ؓ我确实在忽悠?/td> |
[4] | first class typeQ有人翻译ؓ“一{公?#8221;Q我觉得压力巨大 |
[5] | 即高阶函?/td> |
[6] | “字面“主要是针对这些信息会被词法分析程序直接处?/td> |
[7] | q可能导致更多的错误 |
Author: | Kevin Lynx |
---|---|
Date: | 3.13.2011 |
最q一直在学习Lispq门语言。回头一看,基本上接q?个月了。刚开始接触Lisp是因为看 ?lt;Lisp本质>Q然后我发现有很多h宗教般地忠诚q门语言Q于是就来了兴趣?/p>
当然q不是每ơ因为某写得很geek技术文章就d习某个新的技术点。一个月旉Ҏ? 说还是很珍贵了。但是Lispl对是大部分E序员都值得一学的语言Q就像Haskell一P? 我能l出的简单理由包括:
另一斚wQ结合我一个月以来的读书和两个l习工程的实늻历,我觉得也有些理由值得? 不去学习LispQ?/p>
q篇博客我用reStructuredText格式~写Q然后用docutls导出为htmlQ再然后使用q回 用lisp开发的Zmetaweblog API的博客客LQ自动发布到CPPBLOG?/p>
我就摘录些书上的观点(历史)Q?/p>
我可q没到把Lisp捧上天的地步。如果Lisp如此之好Qؓ什么用的h不多Q?lt;Land Of Lisp> 里作者恰好对q个问题做了回答(bla bla blaQ懒得细??/p>
Lisp不像有些语言Q有个直接的机构来维护。感觉它更像C/C++一P只有个标准,然后? 若干~译器(解释器)实现。Lisp在几十年的发展中Q生了很多U方a。方a也就是Ş? 不变的语言变种Q本文说的Lisp均指Lisp的方aCommon Lisp。另一个比较有名的方言? SchemeQ关于各个方a的特点,<Land Of Lisp>里也l了一个图片:
其中Q最左边那只wolf是Common LispQ右辚w只sheep是Scheme?/p>
要学习LispQ首先就是选择方言。然后最重要的就是选择一个编译器实现。世界上知名的有 十几U实玎ͼ也许更多Q。一些商业版本非常强大,甚至能编译出很小的本C码执行文? Q不qhg不菲。当然也有很多开源免费的实现Q例如CLISP、SBCL。我选用的是SBCL?/p>
SBCL交互式命令行不支持括号匹配,甚至没有输入历史。要实现q两个功能,可以装一? lisp工具Qlinedit。在lisp的世界中Q要获得一个lisp的库实在不是件方便的事。尤其是 q些免费的编译器实现Qƈ不像有些语言一P直接随编译器带个几十M的库?/p>
然后有了quicklispq个工具。该工具像Ubuntupȝ里的软g理器一P你可以在 lisp里直接获取某个库。quicklisp查该库是否存在,不存在直接从它的服务器上下蝲? 然后自动安装?/p>
此外Q在lisp的世界里Q写出来的程序不再是跨OS。OS的差异由~译器实现来解决。但是, 写lispE序却需要考虑跨编译器实现Qegg hurtQ。这也是个无比伤的事,比跨OS更伤 。因为OS那么几个,但lisp的编译器实现Q流行的也有好几个?/p>
lisp的世界里Q工E组l也有特D的一套,像makefile一Pq就是asdf?/p>
像我们这U基本没接触qWeb开发的人,可能完全没有思\dC个博客客L。事实上 实现h非常单?/p>
使用q其他博客客LQ例如Windows Live writerQ的定知道metaweblog APIQ在? |客L的时候需要填入。例如CPPBLOG的这个地址是 http://m.shnenglu.com/kevinlynx/services/metaweblog.aspx。这个页面展CZ一些API 说明。这些API是博客客户端和服务器进行操作通信的接口。意思是_服务器端提供q? q些接口Q我们的客户端调用这些接口即可。例?
blogger.deletePostQ调用该接口卛_删除一博客文?
但是客户端如何调用到q个接口呢?q需要通过一U新的技术(或者说标准Q,?xml rpc
。rpc大家应该清楚Qxml rpc其实说白了, 是把接口调用的l则塞进 http
h发给web服务器,服务器接收请求完成操作后再把l果以http回应的Ş式丢l客LQ?
卛_成了一ơ接口调?/strong> ?/p>
至于httph回应的细则就不提了,无非是一些特D格式的数据Q通过tcpq接与服务器
交互q些数据?/p>
所以,基本上,整个q程q是非常单。如何来调用细节塞qhttphQ则是以xml rpc
标准来做Q其格式正好是xml格式。D个例子吧: 当然q部分数据之前就是若qhttph的数据。服务器回应也是以xml格式l织: 我们的博客客L所要做的,是把这些博客发布相关的操作装h提供l用者。底?
实现主要包括httph、xml-rpc的组l等。何况,q两部分在各个语a里都有大量的库存
在,lisp自然也有?/p>
我这里直接选取了lisp的一个xml-rpc库:s-xml-rpcQ基本上百来行代码就可以把各个功
能跑一遍。例如以下lisp代码实C通过s-xml-rpc删除CPPBLOG的一文? 发布博客也很单,Ҏmetaweblog API接口的说明,发布博客旉要填充一个结构体。但
主要涉及到的数据仅包括:文章内容、文章标题、文章分c(可选): 值得注意的是Q如果文章中有脓图,则需要事先将囄文g上传到服务器。CPPBLOG?
metaweblog API里恰有API提供: 该函数读入图片文Ӟ然后调用metaWeblog.newMediaObject接口Q即可完成上传。上传成
功后Q服务器会返回该囄的URL。然后在我们的文章中可以用该囄了?/p>
<?xml version='1.0'?>
<methodCall>
<methodName>title_or_id</methodName>
<params>
</params>
</methodCall
<?xml version='1.0'?>
<methodResponse>
<params>
<param>
<value><string>Welcome to Zope.org</string></value>
</param>
</params>
</methodResponse>
(defun delete-post (postid)
(rpc-call
"blogger.deletePost"
postid
"kevinlynx"
"password"
t))
(defun new-post (title context &optional (cates))
(rpc-call
"metaWeblog.newPost"
""
"kevinlynx"
"password"
(new-post-struct title context cates)
t))
(defun new-media-object (filename)
(rpc-call
"metaWeblog.newMediaObject"
""
"kevinlynx"
"password"
(new-media-object-struct filename)))
仅仅metaweblog的一些接口做装Q对于一个可以用的博客客户端来说还q远不够。大 部分同类工具都有一个友好的GUI~辑界面。我q不打算弄一个编辑界面出来,吃力不讨? 的事情?/p>
我的打算是先用其他工具对文章做排版处理,最后导Zؓhtml格式。因为CPPBLOG支持直接 发布一个html文g。然后在用这个lisp工具整个文件作为博客文章内容发布?/p>
恰好公司最q打用reStructureText(rst)格式来编辑文档,作ؓ熟悉手段Q我军_拿这? 来练手。rst格式非常单,同wiki命o很相伹{在vim里编辑该文g非常合适,因ؓ默认? 持。见?
由图卛_看出Qrst是一U半所见即所得的格式。即Q它遵@你在~辑器里的排版,同时? 通过一些tagQ例如imageQ来控制更丰富的输出?/p>
rst有很多前端工P可以rst文g输出Q例如rst2html.py可以输Zؓhtml。好吧,最 最l我们得Chtml格式的博客文章?/p>
但是如果文章中出C囄Q而图片基本上在本圎ͼ转成html后也是相对\径。我需要我? lisp writer(cl-writer)能自动扫描文章,发现有图片的地方Q就自动图片上传。最恶心 的是上传后还得替换图片引用\径。这个工作可以在rst格式上做Q也可以在结果格式html 上做。通过xml解析库解析html比直接解析rst格式更简单,q且在扩展性上更好?/p>
最l这个html中图片\径替换工作只消耗了不到100行lisp代码。这在很大程度上也依赖于 s-xml库的接口设计?/p>
最l封装好的发布接口如下,从这里也可以看出Q函数式语言ȝ我们写出功能单一代码? 短小的接?
(defun writer-post-new (post-file &key (u (get-default-user))(cates)) (read-post-file u post-file context title (new-post u title context cates)))
别指望我发布的代码能够让你一键在你的博客上留?quot;this is a test"Q你甚至别指望它? 能够工作。但如果你本来就是一个资qlisperQ或者虽然不是lisper但却执意想看看结? 。这里我q要说说如何让q些代码Ƣ乐h:
OS Ubuntu10.04Q下载安装SBCLQ不会有问题Q?/p>
下蝲安装quicklispQ官Ҏ档hand by handQ简单不会有问题Q?/p>
SBCL交互环境中用quicklisp安装s-xml-rpc:
(ql:quickload "s-xml-rpc")
装蝲我的代码:
(asdf:load-system :cl-writer)
在home下添加配|文?cl-writer.lispQ配|你博客信息Q例?
(in-package cl-writer) (setf *default-user* (make-cppblog-user "账户?quot; "密码"))
如果你的博客不在CPPBLOGQ虽然也怹是metaweblogQ但我不能保证成功,配置文g? 要复杂点:
(setf *default-user* (make-user-info :name "帐户?quot; :password "密码" :host "m.shnenglu.com" :url "/kevinlynx/services/metaweblog.aspx"))
SBCL交互环境下测?
(in-package cl-writer) (new-post (get-default-user) "this is a test" "title")
最后,l于敲完q篇文章Q我需要通过以下步骤来发表它:
in shell: rst2html.py lisp_xml_rpc.rst lisp_xml_rpc.html in SBCL: (writer-post-new "lisp_xml_rpc.html")