在上一篇文章中,我用lambda实现了一个快速排序的算法,这个算法的实现和大部分利用索引来实现的算法不同,它没有使用变量的赋值和修改,相反的是,它只有纯函数式的逻辑流程,而且从本质上来说,我们的语言构建块,已经改变了,加号有了一个新的含义,它可以在时间上完全分离,生成一个全新的版本,而不会对现有的系统产生破坏性的作用,由于我们的语言是基于消息和中缀式的数学定义,那么它的使用,看起来和本身系统内部的原生函数,并没有太大的不同,我们的系统的可分解性,达到了语言级别,每一个消息的参数响应对象,都是在上下文中约定的,我们可以再添加无穷多个递归的消息栈,它们都可以利用同一个符号来表达不同的含义。
在smalltalk中,这个语法被实现为左结合,实际上还有更初始的方法,就是使用数学符号的运算符结合,这个思想大致可以在sasl,miranda,erlang,prolog和haskell中找到,由于alan kay受到lisp的影响比较大,所以采用了这样的定义,我们的新函数式语言,希望通过重新给数学符号添加定义,从而给这个新的逻辑工具带来不一样的样子,我们人思考问题,也是具有上下文的,这些上下文,最终把动态的规则构成了一个静态的形象,比如说直角坐标系,函数图像来表现一个物体的状态,等等等等,一个函数就是一个全局的状态参量,它精确描述了一个系统每一个瞬间的状态,使用它们之间复杂的递归关系来对整个世界进行建模。
我们的系统就是在lambda函数的基础上,添加这样一个分离式的时态逻辑的语法糖,它每一个新的表达式,就是给系统增加新的规则,一个规则定义了一个概念在某个上下文的特定含义,这个形式我们使用三种方法来描述,第一种是scheme版本,(lambda (X) E),第二种是miranda版本,f(X) = E,第三种是smalltalk版本,f X1 = E1, f X2 = E2...,需要注意的是,smalltalk版本虽然很好,但是它可能在某些地方不太简单,所以我们使用miranda版本来作为一个替代,miranda版本看起来更像我们普通人写一个函数该有的样式,然后我们的基础库的表现形式也是smalltalk样式,但是这些基础操作符和对象,它们都是纯函数式的,也就是说,它们用起来虽然是smalltalk的语法,但是它不具备可修改性,它们可以在递归过程中返回一个新的自身对象,也就是self这个对象,它是在时间上解耦了的,所以不会出现持续递归的现象,这种对系统的抽象导致它看起来也具有状态,然而它自己已经在时间中消失了,取代它的是它的一个新的对象,这个对象也不具备状态,但是它可以被用来生成新的对象。
我们先来看第一个程序,平方的写法:
//定义
square := f(x) = (x * x);
//使用
square 5;
= 25
第二个程序,阶乘:
//第一种写法
fac := f 1 = 1, f(n) = n * (fac (n - 1));
//第二种写法
fac := f(n) = n == 1
true: 1
false: n * (fac (n-1));
//第三种写法
fac 1 = 1;
fac (n) = n * (fac (n - 1));
因为f(x) = E的形式可以写为一个匿名函数,所以我们可以这样:
(f(x) = (x * x)) 5
= 25
因为列表实际上是一种闭包对象,但是我们专门用一个符号来代表列表,这个符号就是[],[1 2 3] = [1. 2. 3. [] ],这是一个链表的结构。
然后我们可以支持模式匹配和列表解析,也就是说,列表可以提前解析
h.t = [1.2.3.[]];
h
=1
t
=[2.3.[]]
然后我们可以给列表的第一个元素添加标签,从而给系统添加新的匹配规则:
vec.h1.t1 * vec.h2.t2 = (h1 * h2) + (t1 * t2) ;
vec.h1.[] * vec.h2.t2 = 'err' ;
vec.h1.t1 * vec.h2.[] = string.[$e $r $r];
vec.[] * vec.[] = 0 ;
我们把第二个err提示写为string.[$e $r $r],那么大家大概能理解内部的一些实现,上面这几条规则,定义了向量的乘法。
然后我们可以写一个新的大于规则,列表的大于规则。
h.t > x = h > x true: [h] false: [] + t > x;
[] > x = [];
这个规则匹配起来会是这样
[1 2 3 4 5 6] > 2 ;
= [3 4 5 6]
然后我们可以使用宏定义,实际上宏定义并不特殊,它就是一个规则生成模板。
gen-compare compare :=
h.t compare x = h compare x true: [h] false: [] + t compare x,
[] compare x = [];
这样我们就可以定义其他的运算符
gen-compare <=;
gen-compare <;
gen-compare >=;
有了这些之后,我们还想要什么呢,我们可能想知道,list + list这个是什么意思,也就是说
[1 2 3 4] + [2 3 4 5]
= ?
也许我们的List可能是集合,也可能是向量,还有可能是字符,还可能是矩阵元素,这些都无所谓,我们只需要加标签就可以,但是我们现在可以定义哪些运算呢,我们可以有集合,有向量,有这个列表。
//列表的加法
[h.t] + [x] = [h] + (t + [x])
[h] + [x] = [h x]
//向量的加法
vec.[h1.t1] + vec.[h2.t2] = vec.([(h1 + h2)] + (t1 + t2))
vec.[] + vec.[] = vec.[]
有了这些构建工具之后,我们可以写一些程序了,比如说快速排序
h.t qst = (h <= t qst) + [h] + (h > t qst)
[] qst = []
矩阵乘法
//定义一个矩阵,它的行是r,列是c,数据是d
mk-mx r c d :=
f r = r ,
f c = c ,
f(r: c:) = (d ((r: * c) + c:));
//这里之所以可以这么写是因为list的实现内部可以利用索引直接作为参数获取它的值
//list本身是一个函数,具体实现可以参考上一篇文章的my-list对象。
//定义一个方法,取得矩阵的某列
c-r mx c-i =
(c-l i = i == mx r
true: []
false: [(mx r:i c:c-i)] + (c-l (i + 1)))
c-l 0;
//定义一个方法,取得某行
r-f mx r-i =
(r-l i = c-i == mx c
true: []
false: [(mx r:r-i col:i)] + (r-l (i + 1)))
r-l 0;
//这是乘法,它从A,B矩阵中得到另一个新的矩阵
mx.A * mx.B = (A c) == (B r)
(true:
f r = A r ,
f c = B r ,
f(r: c:) = vec.(r-f A r:) * vec.(c-f B c:))
false: 'err';
可以看出来,这个时间分离的递归方法完全用函数的思维去对数据进行建模,因为所有数据都是函数,那么它的上下文可以无限的精简,以至于写出来的每一个程序就是一门语言,它的意义是上下文相关的,所以具有极端的伸缩性。