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

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

#include <stdio.h>
#include <string.h>
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给了它,它自己知道怎么算出来,它描述了将要执行的计算,这里没有可怕的循环和大量的递归,只有足够多的描述,它精确地描述了,矩阵的行和列到底是什么,向量的乘法到底是啥,矩阵的定义到底是什么,矩阵乘法的定义到底是什么,它就可以计算了,它是如此地优雅,以至于让人想到了万物的运行状态,到底是一系列的状态变化,还是即将执行的计算对象,每一次计算都带来了新的数据,假如不计算,你无法确定它到底是什么状态,每一次计算,你就可以知道它内部到底有什么。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354