可计算函数如何计算矩阵乘法

因为前段时间研究了很久的图形学,也写了很多向量和矩阵的运算函数,但是其中一些程序的编写和设计难度无疑让我很难受,由于之前看了alan kay写的关于smalltalk80的历史,我突然意识到使用lambda来计算矩阵,或许会有更简单的方法,相比于alan kay的一切都是对象的思想,它达到了无限的伸缩性,对数字和数据结构都达到了很好的建模,但是奇怪的一点是,使用面向对象语言解决基础算法,好像并没有实质性的进步,我们先来看一个c程序:

#include 
#include 
int bank(char * msg, int val){
  static balance = 0;
  if(strcmp(msg,"set") == 0)
    balance = val;
  else if(strcmp(msg,"do") == 0)
    balance += val;
  else if(strcmp(msg,"get") == 0)
    return balance;
}
int main(){
  bank("set",10);
  bank("do",10);
  printf("%d",bank("get",0));
  return 0;
}

这个c程序封装了一个static变量balance,外部环境可以通过 char * msg 来访问它,但是我们的程序还是基于变量这一个前提,无论是在smalltalk内部,还是java,都保留了内部状态,这样的程序实际上只带来了非破坏性,也就是把程序使用通信的方式组织在一起,拿到消息的每个部分只需要对接口编程,而不需要对实现编程,提高了程序的利用率,降低了总体代码变化的部分,从而降低了实现功能的编写难度,并没有真正从算法的角度去改变程序的内部结构,在面对一个复杂的大型程序带来的复杂性,这样的方法论是很好的,但是实际上,我们还可以更进一步思考,我们的程序实际上并不是在通信,取而代之的是,它们是在计算。
有人说,这不是废话吗?但是仔细想想,我们的用户使用某个软件,到底是在干嘛,他们是在通信吗,还是说他们在计算?其实可以推而广之,我们人类获取知识,改造现实世界,计算更普遍,还是通信更普遍,可能不同的人有不同的结论,但是两者在某些时候是等同的,比如说,一个正在建造航天飞机的工程师,他急需解决一个空气动力学方程从而设计机翼的宽度,他有可能会选择自己计算出来,他也可以找某个团队里的数学很好的助手,计算出来,他只要知道结果就好了,然而这个助手的可信度,或许比起计算机,会有一定的差异,他有可能更相信某个科学计算软件,也可能正好相反,但是可以知道的是,他自己计算出来,和借助某些外部力量知道了这个方程的解,只取决于他的信任度,答案只是一个比特位描述的符号或者方程式,他只需要关注它的可信度,到底是计算得来,还是某种通信得来,两者没有本质上的区别,他只是描述出了问题,然后想知道答案,只要答案的可信程度够高,他就达到了自己的目的,半个月之后,机翼制作完成,他可以做实机试飞实验了,他早已经忘记那个微不足道的微分方程,但是某一天,他可能又要重新捡起来,他可能已经记录在了某个记事本上,但是他已经忘记是哪个本子了,但是他有一个好习惯,就是给自己的笔记本写时间,只要翻阅某段时间内的笔记,他肯定可以找到它,我们发现了某个找回信息的普遍的方法论,我们通过给信息添加标签,它只要是某个类型的信息,可以有不同的查询方式,这样就可以把固定查找的过程,转化为模糊查询的过程,我们只是想要答案,并且定位它,给定了一个范围以后,并不需要准确地记住它的位置,需要的时候再去查询,我们的大脑便可以降低思考的难度,我们再来看范围到底是什么,最初范围这个词和类型是联系到一起的,一个类型的概念,它包含了无数它的实例,这些实例都是它涵盖的范围,我们只需要知道这个范围,它映射到更大更明确的内涵,该微分方程属于某个航天飞机项目,这个项目属于某段时间,记事本都是这个时间的,所以我们肯定可以在那里找到它。
接下来是我们的另一个c语言程序,它表示了一个矩阵计算

void mul_matrix(int M,int N, int K,int**A,int**B,int**C){
  for (i = 0; i < M; i++){
    for (j = 0; j < N; j++){
          int sum = 0;
          for (m = 0; m < K; m++)
            sum = sum + A[i][m] * B[m][j];
          C[i][j] = sum;
    }
  }
}

看起来这个程序好像很简单,实际上它的编写隐含的都是机器的思维,它理解起来是非常深奥的,以至于很多人会看着它深思很久,因为它不包含任何的充足的概念内涵,它就是数组,和矩阵扯不上任何关系,甚至和向量也没有任何关系,它是如此晦涩和陌生,循环,加减乘除,控制条件,这些原始的部分死死地连接到了一起,无法使用更好的办法分开它们,并且每一次的计算都写死在了算法给定的数组里,所以说用c语言,甚至c++和java等编程,它们是等价的,算法的部分无法用更好的方式分解,它们死死地耦合在一起,一个问题无法被分解为某些相同的计算部分,从而让每一次的思考都重复出现,人的大脑提前计算了一次,然后再进入了计算机运算。
实际上人脑思考问题并不是这样的方式,人脑擅长模糊思考,通过一个概念来引用它的范围,从而确定它包含的内容,每一个计算都可以用足够的信息来描述它的运作方式,把计算过程设计为通信,而不是计算,要获取数据,则向函数发送一条信息,它返回一个你需要的信息就是数据,lambda演算很好的做到了这一点,它并不执着于把每一条数据都提前计算好,而是用到的时候再拿出来,由于具有足够多的描述信息来确定它的范围,所以确定内容的时候再计算,可以避免提前把每一个数据都演算一遍,于是大大降低了思考的难度,这是用scheme实现同样的c代码的功能

(define (mul-vec v1 v2)
  (if (= (length v1) (length v2))
      (if (null? v1) 0
      (+ (* (car v1) (car v2))
         (mul-vec (cdr v1) (cdr v2))))
      'error))

(define (make-matrix row col data)
  (lambda msg
    (if (= (length msg) 1)
    (cond
     ((eq? (car msg) 'row) row)
     ((eq? (car msg) 'col) col))
    (list-ref data (+ (* (car msg) col) (cadr msg))))))

(define (col-ref matrix col-index)
  (define (col-loop matrix row-index)
    (if (= row-index (matrix 'row))
    '()
    (cons (matrix row-index col-index) (col-loop matrix (+ row-index 1)))))
  (col-loop matrix 0))

(define (row-ref matrix row-index)
  (define (row-loop matrix col-index)
    (if (= col-index (matrix 'col))
    '()
    (cons (matrix row-index col-index) (row-loop matrix (+ col-index 1)))))
  (row-loop matrix 0))

(define (mul-matrix A B)
  (if (= (A 'col) (B 'row))
      (lambda msg
    (if (= (length msg) 1)
        (cond
         ((eq? (car msg) 'row) (A 'row))
         ((eq? (car msg) 'col) (B 'col)))
        (mul-vec (row-ref A (car msg)) (col-ref B (cadr msg)))))
      (lambda msg 'error)))

代码好像长了很多,但是实际上上面的代码可以用语法糖的形式实现

(m ([mx i j _] * [mx j k _]) n) = (m [mx i j _]) * ([mx j k _] n)

主要的mul-matrix函数只做了一个事情,把A的行和B的列进行向量相乘的函数作为一个计算结果,每一次使用该函数,就是获取它的元素,由于之前我们把list等等都实现为lambda函数,所以相乘之后的矩阵函数实际上性质上等同于make构造的函数,整个系统无缝地揉合在了一起,然而我们只是描述了某些规则,它并没有把所有的元素都计算好,我们并不需要都去计算好,我们只是把A和B给了它,它自己知道怎么算出来,它描述了将要执行的计算,这里没有可怕的循环和大量的递归,只有足够多的描述,它精确地描述了,矩阵的行和列到底是什么,向量的乘法到底是啥,矩阵的定义到底是什么,矩阵乘法的定义到底是什么,它就可以计算了,它是如此地优雅,以至于让人想到了万物的运行状态,到底是一系列的状态变化,还是即将执行的计算对象,每一次计算都带来了新的数据,假如不计算,你无法确定它到底是什么状态,每一次计算,你就可以知道它内部到底有什么。

你可能感兴趣的:(可计算函数如何计算矩阵乘法)