找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 1246|回复: 6

[精彩文萃] 为什么我喜欢LISP语言

[复制链接]

已领礼包: 19个

财富等级: 恭喜发财

发表于 2014-12-2 23:25:18 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
这篇文章是我在 Simplificator――我工作的地方――的一次座谈内容的摘录,座谈的题目叫做“为什么我喜欢Smalltalk语言和Lisp语言”。在此之前,我曾发布过一篇叫做“ 为什么我喜欢Smalltalk?”的文章。
Lisp是一种很老的语言。非常的老。Lisp有很多变种,但如今已没有一种语言叫Lisp的了。事实上,有多少Lisp程序员,就有多少种Lisp。这是因为,只有当你独自一人深入荒漠,用树枝在黄沙上为自己喜欢的Lisp方言写解释器时,你才成为一名真正的 Lisp程序员
目前主要有两种Lisp语言分支: Common LispScheme,每一种都有无数种的语言实现。各种Common Lisp实现都大同小异,而各种Scheme实现表现各异,有些看起来非常的不同,但它们的基本规则都相同。这两种语言都非常有趣,但我却没有在实际工作中用过其中的任何一种。这两种语言中分别在不同的方面让我苦恼,在所有的Lisp方言中,我最喜欢的是 Clojure语言。我不想在这个问题上做更多的讨论,这是个人喜好,说起来很麻烦。

Clojure,就像其它种的Lisp语言一样,有一个REPL(Read Eval Print Loop)环境,你可以在里面写代码,而且能马上得到运行结果。例如:
15

2;=> 5

3

4"Hello world"

5;=> "Hello world"



通常,你会看到一个提示符,就像 user> ,但在本文中,我使用的是更实用的显示风格。这篇文章中的任何REPL代码你都可以直接拷贝到 Try Clojure运行。
我们可以像这样调用一个函数:
1( println "Hello World" )

2; Hello World

3;=> nil



程序打印出“Hello World”,并返回 nil 。我知道,这里的括弧看起来好像放错了地方,但这是有原因的,你会发现,他跟Java风格的代码没有多少不同:
1println ( "Hello World" )



这种Clojure在执行任何操作时都要用到括弧:
1(+ 1 2)

2;=> 3



在Clojure中,我们同样能使用向量(vector):
1[ 1 2 3 4 ]

2;=> [ 1 2 3 4 ]



还有符号(symbol):
1'symbol

2;=> symbol



这里要用引号('),因为Symbol跟变量一样,如果不用引号前缀,Clojure会把它变成它的值。list数据类型也一样:
1'(li st)

2;=> (li st)



以及嵌套的list:
1'(l (i s) t)

2;=> (l (i s) t)



定义变量和使用变量的方法像这样:
1( def hello-world "Hello world" )

2;=> # 'user /hello-world

3

4hello-world

5;=> "Hello world"



我的讲解会很快,很多细节问题都会忽略掉,有些我讲的东西可能完全是错误的。请原谅,我尽力做到最好。
在Clojure中,创建函数的方法是这样:
1( fn [ n ] (* n 2))

2;=> #<user$eval1$fn__2 user$eval1$fn__2@175bc6c8>



这显示的又长又难看的东西是被编译后的函数被打印出的样子。不要担心,你不会经常看到它们。这是个函数,使用 fn 操作符创建,有一个参数 n 。这个参数和2相乘,并当作结果返回。Clojure和其它所有的Lisp语言一样,函数的最后一个表达式产生的值会被当作返回值返回。
如果你查看一个函数如何被调用:
1( println "Hello World" )



你会发现它的形式是,括弧,函数,参数,反括弧。或者用另一种方式描述,这是一个列表序列,序列的第一位是操作符,其余的都是参数。
让我们来调用这个函数:
1(( fn [ n ] (* n 2)) 10)

2;=> 20



我在这里所做的是定义了一个匿名函数,并立即应用它。让我们来给这个函数起个名字:
1( def twice ( fn [ n ] (* n 2)))

2;=> # 'user /twice



现在我们通过这个名字来使用它:
1(twice 32)

2;=> 64



正像你看到的,函数就像其它数据一样被存放到了 变量 里。因为有些操作会反复使用,我们可以使用简化写法:
1( defn twice [ n ] (* 2 n))

2;=> # 'user /twice

3

4(twice 32)

5;=> 64



我们使用if来给这个函数设定一个最大值:
1( defn twice [ n ] ( if (> n 50) 100 (* n 2))))



if操作符有三个参数:断言,当断言是true时将要执行的语句,当断言是 false 时将要执行的语句。也许写成这样更容易理解:
1( defn twice [ n ]

2( if (> n 50)

3100

4(* n 2)))



非常基础的东西。让我们来看一下更有趣的东西。
假设说你想把Lisp语句反着写。把操作符放到最后,像这样:
1(4 5 +)



我们且把这种语言叫做Psil(反着写的Lisp...我很聪明吧)。很显然,如果你试图执行这条语句,它会报错:
1(4 5 +)

2;=> java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)



Clojure会告诉你 4 不是一个函数(函数是必须是 clojure.lang.IFn 接口的实现)。
我们可以写一个简单的函数把Psil转变成Lisp:
1( defn psil [ exp ]

2(reverse exp))



当我执行它时出现了问题:
1(psil (4 5 +))

2;=> java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0)



很明显,我弄错了一个地方,因为在psil被调用之前,Clojure会先去执行它的参数,也就是 (4 5 +) ,于是报错了。我们可以显式的把这个参数转化成list,像这样:
1(psil '(4 5 +))

2;=> (+ 5 4)



这回它就没有被执行,但却反转了。要想运行它并不困难:
1( eval (psil '(4 5 +)))

2;=> 9



你开始发现Lisp的强大之处了。事实上,Lisp代码就是一堆层层嵌套的列表序列,你可以很容易从这些序列数据中产生可以运行的程序。
如果你还没明白,你可以在你常用的语言中试一下。在数组里放入2个数和一个加号,通过数组来执行这个运算。你最终得到的很可能是一个被连接的字符串,或是其它怪异的结果。
这种编程方式在Lisp是如此的非常的常见,于是Lisp就提供了叫做 宏(macro) 的可重用的东西来抽象出这种功能。宏是一种函数,它接受未执行的参数,而返回的结果是可执行的Lisp代码。
让我们把psil传化成宏:
1( defmacro psil [ exp ]

2(reverse exp))



唯一不同之处是我们现在使用 defmacro 来替换 defn 。这是一个非常大的改动:
1(psil (4 5 +))

2;=> 9



请注意,虽然参数并不是一个有效的Clojure参数,但程序并没有报错。这是因为参数并没有被执行,只有当psil处理它时才被执行。 psil 把它的参数按数据看待。如果你听说过有人说Lisp里代码就是数据,这就是我们现在在讨论的东西了。数据可以被编辑,产生出其它的程序。这种特征使你可以在Lisp语言上创建出任何你需要的新型语法语言。
在Clojure里有一种操作符叫做 macroexpand ,它可以使一个宏跳过可执行部分,这样你就能看到是什么样的代码将会被执行:
1(macroexpand '(psil (4 5 +)))

2;=> (+ 5 4)



你可以把宏看作一个在编译期运行的函数。事实上,在Lisp里,编译期和运行期是杂混在一起的,你的程序可以在这两种状态下来回切换。我们可以让psil宏变的罗嗦些,让我们看看代码是如何运行的,但首先,我要先告诉你 do 这个东西。
do 是一个很简单的操作符,它接受一批语句,依次运行它们,但这些语句是被整体当作一个表达式,例如:
1( do ( println "Hello" ) ( println "world" ))

2; Hello

3; world

4;=> nil



通过使用do,我们可以使宏返回多个表达式,我们能看到更多的东西:
1( defmacro psil [ exp ]

2( println "compile time" )

3`( do ( println "run time" )

4~(reverse exp)))



新宏会打印出“compile time”,并且返回一个 do 代码块,这个代码块打印出“run time”,并且反着运行一个表达式。这个反引号 ` 的作用很像引号 ' ,但它的独特之处是你可以使用 ~ 符号在其内部解除引号。如果你听不明白,不要担心,让我们来运行它一下:
1(psil (4 5 +))

2; compile time

3; run time

4;=> 9



如预期的结果,编译期发生在运行期之前。如果我们使用 macroexpand ,或得到更清晰的信息:
1(macroexpand '(psil (4 5 +)))

2; compile time

3;=> ( do (clojure.core/ println "run time") (+ 5 4))



可以看出,编译阶段已经发生,得到的是一个将要打印出“run time”的语句,然后会执行 (+ 5 4) 。 println 也被扩展成了它的完整形式,clojure.core/println ,不过你可以忽略这个。然后代码在运行期被执行。
这个宏的输出本质上是:
1( do ( println "run time" )

2(+ 5 4))



而在宏里,它需要被写成这样:
1`( do ( println "run time" )

2~(reverse exp))



反引号实际上是产生了一种模板形式的代码,而波浪号让其中的某些部分被执行((reverse exp) ),而其余部分被保留。
对于宏,其实还有更令人惊奇的东西,但现在,它已经很能变戏法了。
这种技术的力量还没有被完全展现出来。按着" 为什么我喜欢Smalltalk?"的思路,我们假设Clojure里没有 if 语法,只有 cond 语法。也许在这里,这并不是一个太好的例子,但这个例子很简单。
cond 功能跟其它语言里的 switch 或 case 很相似:
1( cond (= x 0) "It's zero"

2(= x 1) "It's one"

3:else "It's something else" )



使用 cond ,我们可以直接创建出 my-if 函数:
1( defn my- if [ predicate if -true if -false ]

2( cond predicate if -true

3:else if -false))



初看起来似乎好使:
1(my- if (= 0 0) "equals" "not-equals" )

2;=> "equals"

3(my- if (= 0 1) "equals" "not-equals" )

4;=> "not-equals"



但有一个问题。你能发现它吗? my-if 执行了它所有的参数,所以,如果我们像这样做,它就不能产生预期的结果了:
1(my- if (= 0 0) ( println "equals" ) ( println"not-equals" ))

2; equals

3; not-equals

4;=> nil



把 my-if 转变成宏:
1( defmacro my- if [ predicate if -true if -false ]

2`( cond ~predicate ~ if -true

3:else ~ if -false))



问题解决了:
1(my- if (= 0 0) ( println "equals" ) ( println"not-equals" ))

2; equals

3;=> nil



这只是对宏的强大功能的窥豹一斑。一个非常有趣的案例是,当面向对象编程被发明出来后(Lisp的出现先于这概念),Lisp程序员想使用这种技术。
C程序员不得不使用他们的编译器发明出新的语言,C++和Object C。Lisp程序员却创建了一堆宏,就像defclass, defmethod等。这全都要归功于宏。变革,在Lisp里,只是一种进化。
觉得文章有用?立即: 和朋友一起 共学习 共进步!
本文作者:
而且,对文章有任何想法,可:
正在拼命挖掘沟通路线,马上就通了!


论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

已领礼包: 604个

财富等级: 财运亨通

发表于 2014-12-3 09:04:50 来自手机 | 显示全部楼层
主要是它喜欢我,所以我喜欢它

点评

喜欢你的如果要是很丑,你还会喜欢她吗  详情 回复 发表于 2014-12-3 09:11
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

已领礼包: 40个

财富等级: 招财进宝

发表于 2014-12-3 09:11:38 | 显示全部楼层
/db_自贡黄明儒_ 发表于 2014-12-3 09:04
主要是它喜欢我,所以我喜欢它

喜欢你的如果要是很丑,你还会喜欢她吗{:soso_e120:}

点评

这是一个值得深思的、严肃的、深沉的问题  详情 回复 发表于 2014-12-3 09:41
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

已领礼包: 604个

财富等级: 财运亨通

发表于 2014-12-3 09:41:56 | 显示全部楼层
本帖最后由 /db_自贡黄明儒_ 于 2014-12-3 11:02 编辑
newer 发表于 2014-12-3 09:11
喜欢你的如果要是很丑,你还会喜欢她吗


这是一个很深很深的{:soso_e112:}问题
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

发表于 2015-1-9 22:01:56 | 显示全部楼层
喜欢归喜欢,要完全的弄懂还是很艰难的
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

已领礼包: 2个

财富等级: 恭喜发财

发表于 2015-1-26 09:42:10 | 显示全部楼层
要完全的弄懂还是很艰难的
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

发表于 2015-1-26 14:13:59 | 显示全部楼层
我喜欢她(lisp)主要是随呼随到{:soso_e120:},用记事本就可以开始编写了
而且不会像ARX一样范政治错误(致命错误)
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|辽公网安备|晓东CAD家园 ( 辽ICP备15016793号 )

GMT+8, 2024-9-24 14:34 , Processed in 0.212789 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表