Archive for 2010年12月

达斯勋爵

2010/12/27

周末逛街。

从量子计算到 OpenQL 的一知半解

2010/12/25

量子计算可能是计算领域的下一件大事情。每次关于量子计算的新闻都能引起程序员们对于世界是否会被颠覆的讨论。幸好上帝似乎把这个门槛弄得很高,世界才能继续按照今天的规则运转。既然可能死在这上面,最好搞清除是怎么回事。可是我对量子力学的理解力也就限于《原子中的幽灵》那种科普水平了。加上五年里断断续续看到的文章,此文的一多半纯属猜测。

五年前我对量子计算模模糊糊的理解是利用量子的状态叠加特性,实现天然的并行计算,以至于能在多项式时间计算某些 NP 问题。但是,既然量子状态叠加会在被观察的一瞬间塌陷,所谓天然的并行计算带来的绝对好处也只能存在于结果产生之前。在从输入到结果的整个宏观态过程中,必然有一些缺陷来抵消天然并行计算的绝对好处 —— 这个缺陷是什么,这是五年来我对量子计算的疑惑。最近几个月根据新看的资料慢慢猜测,这个缺陷可能就是量子计算的结果并不能保证每次都正确,正确的概率是一个比较接近于一的数值。至于计算结果的不确定性是怎么和状态塌陷过程联系起来的就超出了我的理解能力。但是一般来说,用一种缺陷换取一种好处,已经是普通人能理解的宇宙准则了。基本上大多数人考虑一件事情如何影响自己的生活也只需要这种程度。

虽然量子计算的结果是不确定的,但庆幸的是验证一个给定的结果是否正确一般非常容易。比如,解一个二次方程,和验证一个数是不是一个二次方程的解,显然后者更容易。(但是也有极少数人相信两者一样容易。大多数人相信后者更容易,但迄今无人能证明。这就是著名的 P != NP 问题。)所以重复运行几次量子计算求解加经典计算的验证就能正确解决某些 NP 问题,虽然过程要重复几次并且需要额外的验证步骤,但是这些低阶的开销不会妨碍计算过程的时间复杂度仍然保持在多项式级别。

因此基本上,量子计算类似 GPGPU 计算这样的机制,能解决特定问题,但是不能解决通用计算的所有问题。就像编译器不可能用 GPU 加速,大概应用程序 UI 的代码也不会量子化。量子计算必须依靠经典计算的辅助(就像 GPGPU 需要依靠 CPU 的辅助)。等到量子加速芯片放到 MacBook Pro 里的那一天,世界不会被颠覆,只是系统中会多一套叫做 OpenQL (Q 是 quantum 的 Q)的开发框架。

C 是 MVC 的 C

2010/12/15

开发桌面应用的时候,model-view-controller 是技术讨论中最为频繁引用的概念之一。所以一直想写篇关于 MVC 的。不过总是犹豫如何入手谈论这个复杂的话题。上周记录了一些学习 OpenGL 的想法,其中一篇和 MVC 有关,有人提出,MVC 是不是 over-engineering ?特别是 controller 这个概念是不是多余的?

质疑一:

没有人觉得这是 over engineering 么? 但凡任何一个把界面代码和业务逻辑代码进行一定程度的分离的程序不都有 view 和 model 么?至于二者之间怎么耦合方法可以多种多样,有必要非得显式设计成什么 controller ,而且还得按照 diagram 上的方式一本正经的互动?

我的意思是说,且不说设计模式是好是坏尚无定论,本来 GUI 编程里需要考虑的方方面面就多,如果 system GUI APIs 还非要强迫你去按照什么设计模式来把 M、V、C 的子类 plug in 才能用就未免太死板了(让我想到了当年失败的 EJB)。

质疑二:

我一直觉得 controller 是个多余的设计。

逻辑层提供 API 给显示层调用,提供(支持 multi-cast 的)callback 机制(callback function、event、或是 ugly 一点的 listener/observer)通知显示层。这样,对于显示层来说,主动被动(或称从上往下、从下往上)的情景都涵盖了,不就够了么,要单独搞个 controller 做甚?

就你说的例子,多个 view 同步可以用注册逻辑层的 multi-cast 的 callback 的确保一致性;异步本身可以封装在显示层内部(也就是逻辑层的 API 是同步的,显示层自己实现异步的假象),不关 model 什么事;drag-n-drop 更是显示层局部的代码互调,也不关 model 什么事。

首先,『如果 system GUI APIs 还非要强迫你去按照什么设计模式来把 M、V、C 的子类 plug in 才能用就未免太死板了』这个观点我认为是无的放矢了。我从来没有看到任何一个 framework 会强迫你按照 MVC 来实现一个程序。不管是 Qt 、Cocoa 、MFC 、Win32 SDK ,你总是可以用很简陋的方式去实现你的 UI 。或者如果头脑够健壮,你完全可以用意大利面条的方式实现复杂的 UI 。『EJB』倒的确是一个经典的强迫开发者实现一大堆 over-engineering 接口的例子,只是除了 Sun 当年会做这样的蠢事之外,这种灾难几乎没有被重复过(即便是同门的 AWT/Swing 也没有像 EJB 强迫接口实现那样强迫 MVC)。

至于说『异步本身可以封装在显示层内部』这种说法,我认为主要基于想象而不是实际的工程经验。实际中,异步操作不可能封装在显示层内部,更不要说很多异步操作还要求显示中间状态,这些中间状态的合法性要靠 model 来判断,否则就不是中间状态而是花屏了。

本文主要回答另一个问题:什么是 controller ,controller 是否必要。我初次接触 UI 开发使用的是 MFC 。MFC 的 UI 开发模式官方称为文档-视图(view-document)—— 没有提到 controller 。像 MFC 这类略过 controller 不谈的 framework 还有一些。

什么是 controller ,实体和规则

从下图表明的定义来看,controller 有三个特点:

  1. 把 UI framework 的底层消息翻译成修改 model 的高层命令;
  2. 设置 view 的状态(比如 active/inactive ,谁是 main view),注意 controller 修改的是 view 的一些简单状态,不能触及 view 的主要显示。其实这是 controller 最弱的一个可有可无的功能。
  3. Controller 只能修改 model ,不能直接修改 view 。View 的修改必须由 model 的通知触发,由 view 本身完成。

上面的第一点可以用来划分在一个 UI 系统中什么是 controller :一般来说,那些直接处理底层消息的回调函数就是 controller 。比如,MFC 里 CViewOnCommand() 。Cocoa 里 NSResponder 的 mouseMove() 。Controller 不需要非得是单独的一个或者几个类。那些认为 controller 是『多余』的质疑往往针对是否需要一个或者几个 controller 类,这是对 MVC 的误解。但是,虽然 controller 无需作为单独的类来实现,它的代码与 model 和 view 的划分还是有明确界限,而且 controller 代码的编写必须符合上面描述的第二点和第三点。

要特别说明的一点是,虽然 controller 不需要非得是单独的类,但是在复杂的应用中单独的 controller 类会带来好处。当 view 的不同实例需要对用户的相同操作做出不同的反应的时候(比如显示缩略图和显示稍大preview 的 view 可以是共用同样的 render 代码,但是会提供不同的操作),单独的 controller 类可以让代码更清晰。

所以,controller 只是告诉开发者如何把消息处理函数实现得更好,更容易维护的一个概念。把它理解成强迫你实现一个模块的 over-engineering 有点强迫妄想的。

Controller 不能做什么

一个通常的对 MVC 的误解是 controller 用来控制 model 和 view 。应该采用的规则是:

  1. Controller 修改 model ;
  2. Model 通知 view 发生了改动,但不能通知改动了什么;
  3. View 自己负责更新可视化效果。

第一个问题是 controller 为什么不能修改 view 。答案是 controller 和 view 都不应该实现 submit change 的逻辑。Controller 的职责是把用户的操作(可以看作对 change 的低级描述)翻译成 model 可以接受的高级 change 描述,但是它不能干涉如何把 change submit 到已有的数据中。View 的责任是如实的表现 model 当前状态(确切的说是最近的合法状态),它也绝不应该涉足把一份增量数据合并到已有的数据集中这样的任务。这样的合并可以很简单,但是也经常很复杂,尤其是对于那些一致性约束很复杂的数据。一个应用应该只有一份代码负责这样复杂的逻辑,那就是 model 。

第二个问题是为什么应该由 model 而不是 controller 通知 view 发生了改变。答案是因为只有 model 知道何时应该通知。如果 model 提供的每个操作都是能原子化的保证数据一致的和同步的,那么 controller 调用这样的操作之后立即通知 view 也未尝不可。但是复杂的应用中,model 的操作往往是异步的、后台进行的、和并发的。如上面提到的,这种异步、后台和并发的特性不但是因为各种客观条件,而且有时是主动设计的。比如耗时很长的操作,又需要展示中间状态,就不能做成一个同步的操作。只有 model 能知道什么是最恰当的时间通知 view 进行更新。

暂态数据能不能由 view 管理

如果一份数据仅仅用于显示临时的可视化效果,能否让 view 直接管理?答案是具体情况具体分析。取决于如何定义暂态数据。比如,有些数据仅仅用于一个操作期间的显示效果,比如拖拽,或者长时间的 cache 更新。问题在于这样的长时间操作期间,UI 并不阻止用户发起其它操作,这些新发起的操作可能和原有操作交替或者同时运行,可能新操作的结果部分或者全部受到原有操作结果的影响,或者新操作可能会终止或者暂停原有操作。而这些复杂操作中可能就包括文档的打开或者关闭操作。

所以,暂态数据不是绝对不能由 view 管理,但是不要轻视 model 的作用。很多时候,让你的 controller 恪守第三条准则『Controller 只能修改 model ,不能直接修改 view 。View 的修改必须由 model 的通知触发,由 view 本身完成』,会让代码的整体复杂度大大降低,也能减少今后必须增加 UI 功能时所面临的引入新 bug 的风险。

什么是 controller

Model 和 view 之间的信息流动必须是有序的。如果假设没有 controller ,那么上面那个图里的五个箭头就必须都画在 model 和 view 之间。如果两个实体之间的箭头有五个之多,这些箭头的端点实际附着的地方肯定是不同的。这些附着点就在 model 和 view 就会形成不同的子模快。所以,除非能证明把这五个箭头中的两到三个是多余的,否则你还是不得不从 model 和 view 里重构出一个实体。只有让实体之间的箭头控制在一到两个,一个 pattern 才真正具有降低整体复杂度的意义。这也正是质疑一里所谓『二者之间怎么耦合方法可以多种多样』的问题所在,对于复杂的 UI 系统,『多种多样』并不是实际可以采取的方法,只是没有实际经验的臆想。

上面对 model 和 view 的讨论说明,这五个箭头缺一不可。所以,model 和 view 模块里实际包含着一些子模块来承担不同箭头的附着点。这些子模块的概念就是 controller 。

Controller 不是一个束缚代码物理位置的约定。它是一个设计的概念,帮助开发者理清信息流动的方向。它是一个刻度,让开发者明白 code base 的行为。你可以说,我的 code base 中 controller 很薄(或者很厚)而且我明白原因。但是如果你说:哦,那是 over-engineering ,我一直都把 controller 这个概念从头脑中清除出去,那你实际上不知道自己在做什么。

OpenGL 随想(二):声明式与 MVC

2010/12/10

上篇的结尾提到了声明式 DSL 和 OpenGL 的汇编风格的差别和联系。其实我当时还联想到 model-view-controller 模式,这次另起一篇。

能有这样的联想是因为引入 OpenGL 的产品几乎都是 MVC 架构,而且代码中 OpenGL 相关的部分几乎完全被限制在 view 模块中。具体点说,在基于 Cocoa 的代码中,OpenGL 的使用几乎完全被限制在 NSView 子类的 drawRect: 消息中。在 Win32 程序中几乎完全被限制在 WM_PAINT 的处理函数中。也就是说,复杂的 MVC 架构的应用是用汇编风格来写 view 。另一方面,可以把 model 看成是 view 的声明式描述。

所以可以这样来解释 MVC 模式:每个 model 模块的定义都是一个针对当前应用定制的声明式 DSL ,每个 view 模块都是这个 DSL 的(可视化)解释器。OpenGL 的风格正好符合高效解释器通常需要汇编语言来编写这个惯例。在一个设计良好的 MVC 应用中,不仅是模块功能的分离,而且隐含了两种编程范式(paradigm)—— 声明式和命令式。

如果有一天真的出现了声明式的 3D 引擎又会如何呢?比如 Java 3D 当年就尝试过类似声明式的概念。我想也许到那时 view 模块会成为 MVC 模式中对底层引擎很薄的一层封装(而今天 MVC 中 controller 是最简单的一层)。又或许我们的应用永远需要一个比通用的声明式语言更定制化的 DSL ,而 view 就成为这两种声明式 DSL 的翻译器。

OpenGL 随想(一):C 格式机器码

2010/12/09

自从 2 月份研究了投射矩阵(projection matrix)之后,学习 OpenGL 的事情就放下了好一阵子。直到最近工作上有了实际的需求才重新拾起来。

附带说一下,《OpenGL SuperBible》这本书真的不错,虽然已经多少年对名字含有『Bible』的书敬而远之了。不过纠结了很久是买第四版还是第五版,因为第五版把 OpenGL 3.0 里标为 deprecated 的 fixed-pipeline 部分删除了。但是短期内不好说 OS X 对 OpenGL 3.0 能支持到什么程度。而且实际中也要尽量支持 Windows ,那是个对高版本 OpenGL 更严酷的环境。

每次看 OpenGL 都觉得它的编程风格透着股邪气,这当然有我本人经历的缘故,但也主要是因为 OpenGL APIs 对参数和返回值的使用相对一般的编程风格来说比较少,而对当前状态(current context)的依赖相对较多。比如说纹理贴图操作(texture),一般的风格是把作为纹理的位图载入内存,然后返回一个纹理对象的引用或者 handle ;然后调用贴图的函数,把纹理对象和被贴图的对象作为这个函数的参数(或者贴图函数就是被贴图对象的一个方法)。OpenGL 里,一般先用一个函数把位图调入内存创建一个『当前』纹理对象。但是没有任何引用或者 handle 表示『当前』纹理对象,后续的贴图操作仅仅依靠『当前』这个状态本身来操作。再比如向一个多边形添加一个顶点(vertex),没有任何引用表示这个多边形,你能依靠的只是『当前』多边形。当然用过的人对此也不会太奇怪,因为 OpenGL 标准开宗明义 OpenGL 是一个『状态机』模型。

我初次接触 3D 开发是 7 年前的 Java 3D,使用的是和 OpenGL 完全不同的概念模型,虽然用的是 Java 语言,但是更接近声明式的风格。这也是我觉得 OpenGL 邪门的一个原因。

要说这个概念也不是那么难懂,但是为什么会透出一种邪气呢?我猜大概是从高级语言诞生以来,程序员已经习惯了用参数和返回值进行跨函数的信息传递。而高度依赖上下文属于上一代语言,也就是汇编语言的编程风格。

在高级语言中,借助符号化的变量和逻辑化的语法,可以毫不费力的在一行代码里访问五六个甚至十来个变量。所以借助于这些直接访问变量之外的隐式上下文在高级语言里成为了不必要的事情,尤其因为隐式上下文是加大程序局部复杂度的主要原因。而汇编语言则恰恰相反,需要通过好几条甚至几十条指令才能完成高级语言一行的功能,而每条指令只能操作一两个操作数。所以隐式上下文是不可缺少的。

因为采用状态机模型和汇编的编程风格,所以基于 OpenGL 编写的代码颇有些奇异的风格。比如,今天的代码很少在语法结构之外采用缩进,也就是说,除非是语法级别的结构,比如类的定义、分支、循环、函数定义、或者命名空间等等,一般的语句不会采用缩进。而在基于 OpenGL 的代码里,基于上下文变化的缩进是普遍的习惯。OpenGL 虽然提供 C 函数作为 APIs ,但是本质上和形式上都是显卡的汇编语言。在对性能要求极高的 3D 领域,虽然形式上通过 C 语言提供了跨平台能力,本质上却重演了早期计算机开发注重性能的汇编语言阶段。

OpenGL 的汇编风格还我想到一个在网上讨论中遇到过两三次的观点:声明式(declarative)的 DSL(domain specific language)具有广泛的未来,特别是声明式语言给底层平台的全局优化留下了巨大的空间。OpenGL 的目的是描述一个场景(scene),从问题本身来说是一个非常适合声明式 DSL 的任务,从机制来说也需要最大化利用硬件优化。但 OpenGL 并非声明式而是命令式(imperative),而且并非高级语言的过程式结构化风格而是偏重于汇编风格,我想这也是现实向理想低头的一个例子,毕竟只有程序员才能从各个层次考虑优化。