打印本文 打印本文 关闭窗口 关闭窗口
用javascript实现人工智能脚本
作者:武汉SEO闵涛  文章来源:敏韬网  点击数1879  更新时间:2009/4/23 11:26:57  文章录入:mintao  责任编辑:mintao
tanceof Array)
 {
  var fun = new Function(args,
   "for(var i = 0; i < arguments.length; i++) arguments[i] = LispScript.Run(arguments[i]);return LispScript.Run("+code.toEvalString()+");");

  var globalFuncName = __funList.pop();

  fun._funName = globalFuncName;

  if(globalFuncName != null)
   self[globalFuncName] = fun;

  return fun;
 }

 return [];
};

于是

[defun,subst,[x,y,z],
  [cond,[[atom,z],
         [cond,[[eq,z,y],x],
               [true,z]]],
     [true,[cons,[subst,x,y,[car,z]],
                  [subst,x,y,[cdr,z]]]]]]

偶然地我们在这儿看到如何写cond表达式的缺省子句. 第一个元素是't的子句总是会成功的. 于是
[cond,[x,y],[[_,true],z]]

等同于我们在某些语言中写的
if x then y else z

对于函数调用,具有如下结构:[FunName,[_,args]]
其中FunName是函数名称,[_,args]是指定参数引用列表args
注意[FunName,args]也是合法的,但是和[FunName,[_,args]]有所区别,对于前者,指令在被调用之前先计算args的值,把计算出的值作为参数列表代入函数计算(期望args计算结果为List),而后者的args参数列表在函数指令调用时才被计算

到这里为止我们很高兴地看到LispScript已经可以不依赖于javascript来扩展了

现在我们可以直接用LispScript定义一些新函数了:

函数:[isNull,x]测试它的自变量是否是空表.
LispScript.Run(
 [defun,'isNull',['x'],
  [eq,'x',[_,NIL]]]
);

> [isNull,[_,a]]
[]
> [isNull. [_,[]]]
//t

函数:[and,x,y]返回t如果它的两个自变量都是t, 否则返回[].
LispScript.Run(
 [defun,'and',['x','y'],
  [cond,['x',[cond,['y',true],[true,NIL]],
   [true,NIL]]]]
);

> [and,[atom,[_,a]],[eq,[_,a],[_,a]]]
t
> [and,[atom,[_,a]],[eq,[_,a],[_,b]]]
[]

函数:[not,x]返回t如果它的自变量返回[],返回[]如果它的自变量返回t.
LispScript.Run(
 [defun,'not',['x'],
  [cond,['x',NIL],
        [true,true]]]
);

> [not,[eq,[_,a],[_,a]]]
[]
> [not,[eq,[_,a],[_,b]]]
t

函数:[append,x,y]取两个表并返回它们的连结.
LispScript.Run(
 [defun,'append',['x','y'],
  [cond,[[isNull,'x'],'y'],
    [true,[cons,[car,'x'],['append',[cdr,'x'],'y']]]]]
);

> [append,[_,[a,b]],[_,[c,d]]]
[a,b,c,d]
> [append,[], [_,[c,d]]]
[c,d]

函数:[pair,x,y]取两个相同长度的表,返回一个由双元素表构成的表,双元素表是相应位置的x,y的元素对.
LispScript.Run(
 [defun,'pair',['x','y'],
  [cond,
   [[and,[isNull,'x'],[isNull,'y']],NIL], 
   [[and,[not,[atom,'x']],[not,[atom,'y']]],
    [append,[[[car,'x'],[car,'y']]],['pair',[cdr,'x'],[cdr,'y']]]
   ]]]
);

> [pair,[_,[x,y,z]],[_,[a,b,c]]]
[[x,a],[y,b],[z,c]]

[assoc,x,y]取原子x和形如pair函数所返回的表y,返回y中第一个符合如下条件的表的第二个元素:它的第一个元素是x.

LispScript.Run(
 [defun,'assoc',['x','y'],
  [cond,[[eq,[car,[car,'y']],'x'],[car,[cdr,[car,'y']]]],
   [[isNull,'y'],NIL],[true,['assoc','x',[cdr,'y']]]]]
);

> [assoc,[_,x],[_,[[x,a],[y,b]]]]
a
> [assoc,[_,x],[_,[[x,new],[x,a],[y,b]]]]
new

[ret,e]返回表达式计算结果
LispScript.Run(
 [defun,'ret',['e'],[car,['e']]]
);

[str,e]返回表达式计算结果的引用
LispScript.Run(
 [defun,'str',['e'],[_,[_,'e']]]
);

我们来看一下为什么要定义ret函数:
我想通过前面的解释和实际应用大家已经理解了引用(quote)的重要性,并且很容易证明:[[_,e]] = [e]
现在的问题是我们必须要定义一个引用的反函数f,令[f,[_,e]] = e
而显然地ret正是这样一个函数

[map,x,y]期望x是原子,y是一个表,如果[assoc,x,y]非空返回[assoc,x,y]的值否则返回x
LispScript.Run(
 [defun,'map',['x','y'],
  [cond,[[isNull,[assoc,'x','y']],'x'],[true,[assoc,'x','y']]]]
);

[maplist,x,y]期望x和y都是表,返回由x中的每个元素t求[map,t,y]的结果构成的表
LispScript.Run(
 [defun,'maplist',['x','y'],
  [cond,
   [[atom,[_,'x']],[map,'x','y']],
   [true,[cons,['maplist',[car,[_,'x']],'y'],['maplist',[cdr,[_,'x']],'y']]]
  ]
 ]
);

因此我们能够定义函数来连接表,替换表达式等等.也许算是一个优美的表示法, 那下一步呢? 现在惊喜来了. 我们可以写一个函数作为我们语言的解释器:此函数取任意Lisp表达式作自变量并返回它的值. 如下所示:


LispScript.Run(
 [defun,'_eval',['e','a'],
  [ret,[maplist,[_,'e'],'a']]
 ]
);

_eval.的简洁程度或许超出了我们原先的预想,于是这样我们获得了LispScrip实现的一个完整的自身的解析器!
让我们回过头考虑一下这意味着什么. 我们在这儿得到了一个非常优美的计算模型. 仅用quote,atom,eq,car,cdr,cons,和cond, 我们定义了函数_eval.,它事实上实现了我们的语言,用它可以定义和(或)动态生成任何我们想要的额外的函数和各种文法(这一点比较重要)

其他(略为复杂)的扩展:

下面我们定义变量的赋值操作[setq,paraName,paraValue]
LispScript.Run(
 [defun,'setq',['para','val'],
  [ret,[defun,'para',[],[_eval,'val']]]]
);


增加逻辑操作符or,[or,x,y]返回t如果它的自变量有一个为t,否则返回[]
LispScript.Run(
 [defun,'or',['x','y'],
  [not,[and,[not,'x'],[not,'y']]]]
);

增加循环控制foreach,[foreach,v,[list],[expr]]
foreach期望list是一个表,依次取表中的每一个原子作为expr的参数进行计算,返回计算结果的表
LispScript.Run(
 [defun,'foreach',['v','list','expr'],
  [cond,
   [[isNull,'list'],[]],
   [true,[cons,[_eval,[_,'expr'],[['v',[car,'list']]]],['foreach','v',[cdr,'list'],[_,'expr']]]]
  ]
 ]
);

增加批量赋值操作let,[let,[[a1,v1],[a2,v2]...]]
LispScript.Run(
  [defun,'let',['paralist'],
   [foreach,"'v'",'paralist',[_,[setq,[car,"'v'"],[car,[cdr,"'v'"]]]]]
  ]
);

总结

现在该回过头来看看我们究竟做了什么,以及这么做有什么意义了。
首先我们用javascript实现了一个简单的向下递归的词法分析器,它能对嵌套数组的每个原子进行简单处理,加上几个辅助函数(toEvalString(),Assert(),Element()和一个存放函数名称的堆栈...简单来说我们仅用了数十行代码实现了一种全新的“函数式”语言??LispScript的完整内核。
接着我们定义了7种原始操作,它们分别是quote,atom,eq,car,cdr,cons和cond
然后(相对较复杂地),我们定义了三种用来描述和调用函数的标记,它们分别是lambda, label以及defun,于是我们成功地用另外不到百行代码实现了LispScript语言的核心环境。
接着(接下来的部分已经可以完全独立于javascript)我们用7种原始操作符和函数定义标记defun定义出一些新的函数,分别是:isNull,and,not,append,pair,assoc,ret和str
然后我们惊喜地发现,可以仅用一行LispScript指令定义出自身的“解析器”??_eval函数
最后我们在此基础上定义出一些略为复杂的函数,它们包括:or,setq,foreach和let,其中一些新函数带给我们的新语言定义变量和处理循环的能力,加上前面实现的一些函数,一个比较完善的基础环境就搭建成了。

写在最后:LispScript和Lisp

事实上我们依照[ref. Paul Graham.]的精彩描述用javascript实现了LispScript,毫无疑问,它是一种Lisp(或者Lisp风格的函数式语言),尽管功能上还十分简陋,但它确实是符合Lisp的基本思想和拥有Lisp的基本特性。由于javascript数组文法的特点,我用[]取代了[ref. Paul Graham]中的(),用逗号取代了空格作为分隔符。同[ref. Paul Graham]的文章以及目前一些标准(或者相对标准)的Lisp不同的是,我根据javascript灵活的特点有意弱化了LispScript的语法结构,这样使得LispScript更加灵活,也更加方便实现,然而代价是一小部分的可维护性和安全性。
最后,LispScript还有许多需要完善的内容,例如,最明显地是它基本上还不具有基本的数值运算能力(相对而言,符号操作能力已经比较完善),另外对原子操作参数合法性的检验、副作用, 连续执行 (它得和副作用在一起才有用), 动态可视域、复杂数据结构支持以及注释文法(这相当重要!)也都是它所欠缺的,不过这些功能“都可以令人惊讶地用极少的额外代码来补救”。

感谢约翰麦卡锡,这位天才早在数十年前就向我们展示了一种程序设计领域内至今无人能超越的“极致的美”,他于1960年发表了一篇非凡的论文,他在这篇论文中对编程的贡献有如欧几里德对几何的贡献.1 他向我们展示了,在只给定几个简单的操作符和一个表示函数的记号的基础上, 如何构造出一个完整的编程语言. 麦卡锡称这种语言为Lisp, 意为List Processing, 因为他的主要思想之一是用一种简单的数据结构表(list)来代表代码和数据.

感谢保罗格雷厄姆,他用浅显易懂的语言将Lisp的根源和实质展现在我们面前,令我们能够幸运地零距离体验Lisp的这种“超凡的美”

如果你理解了约翰麦卡锡的eval, 那你就不仅仅是理解了程序语言历史中的一个阶段. 这些思想至今仍是Lisp的语义核心. 所以从某种意义上, 学习约翰麦卡锡的原著向我们展示了Lisp究竟是什么. 与其说Lisp是麦卡锡的设计,不如说是他的发现. 它不是生来就是一门用于人工智能, 快速原型开发或同等层次任务的语言. 它是你试图公理化计算的结果(之一).

随着时间的推移, 中级语言, 即被中间层程序员使用的语言, 正一致地向Lisp靠近. 因此通过理解eval你正在明白将来的主流计算模式会是什么样.

References
The Roots of Lisp Paul Graham. Draft, January 18, 2002.
LISt Primer Colin Allen & Maneesh Dhagat.Tue Feb 6, 2001.(http://mypage.iu.edu/~colallen/lp/lp.html)

上一页  [1] [2] 

打印本文 打印本文 关闭窗口 关闭窗口